[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: isaacrlevin\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n\n## Further details\n- OS: [e.g. iOS]\n- PresenceLight App Type (Desktop, Blazor)\n- PresenceLight Build (Nightly, Store, etc)\n- PresenceLight Version (The # in About)\n- Did you clear settings.json? (C:\\Users\\username\\AppData\\Local\\Packages\\37828IsaacLevin.197278F15330A.SomeValue\\LocalState\\settings.json\n- ASP.NET Core version\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: nuget\n  directory: \"/src\"\n  schedule:\n    interval: daily\n    time: \"10:00\"\n  open-pull-requests-limit: 10\n  ignore:\n  - dependency-name: Blazorise.Bootstrap\n    versions:\n    - 0.9.3\n    - 0.9.3.1\n    - 0.9.3.3\n    - 0.9.3.4\n    - 0.9.3.5\n  - dependency-name: Microsoft.Identity.Client\n    versions:\n    - 4.26.0\n    - 4.27.0\n    - 4.28.0\n    - 4.28.1\n    - 4.29.0\n    - 4.30.0\n  - dependency-name: Blazorise.Icons.FontAwesome\n    versions:\n    - 0.9.3\n    - 0.9.3.1\n    - 0.9.3.3\n    - 0.9.3.4\n    - 0.9.3.5\n  - dependency-name: Microsoft.Identity.Web\n    versions:\n    - 1.6.0\n    - 1.7.0\n    - 1.8.1\n    - 1.8.2\n    - 1.9.0\n  - dependency-name: Microsoft.Identity.Web.MicrosoftGraphBeta\n    versions:\n    - 1.6.0\n    - 1.7.0\n    - 1.8.1\n    - 1.8.2\n    - 1.9.0\n  - dependency-name: Microsoft.Identity.Web.UI\n    versions:\n    - 1.6.0\n    - 1.7.0\n    - 1.8.1\n    - 1.8.2\n    - 1.9.0\n  - dependency-name: Nerdbank.GitVersioning\n    versions:\n    - 3.4.190\n  - dependency-name: Newtonsoft.Json\n    versions:\n    - 13.0.1\n  - dependency-name: YeelightAPI\n    versions:\n    - 1.10.1\n    - 1.10.2\n  - dependency-name: Microsoft.AspNetCore.Authentication.OpenIdConnect\n    versions:\n    - 5.0.3\n    - 5.0.4\n  - dependency-name: Serilog.Extensions.Hosting\n    versions:\n    - 4.0.0\n    - 4.1.0\n    - 4.1.2\n  - dependency-name: Microsoft.ApplicationInsights.NLogTarget\n    versions:\n    - 2.17.0\n  - dependency-name: Microsoft.ApplicationInsights.AspNetCore\n    versions:\n    - 2.17.0\n  - dependency-name: Microsoft.ApplicationInsights.WorkerService\n    versions:\n    - 2.17.0\n  - dependency-name: IdentityModel\n    versions:\n    - 5.0.1\n"
  },
  {
    "path": ".github/workflows/Azure_Blob_Deploy.yml",
    "content": "name: Deploy Worker Apps to Azure Blob Storage\n\non:\n  workflow_dispatch:\n    inputs:\n      target:\n        description: 'Target of Channel Name to Sign'\n        required: true\n        default: ''\n        type: string\n\n  workflow_call:\n    inputs:\n      target:\n        description: 'Target of Channel Name to Sign'\n        required: true\n        default: ''\n        type: string\n\njobs:\n\n  Deploy_Worker_Artifacts:\n    name: Deploy Worker Artifacts\n    environment:\n        name: Deploy_Azure_Blob\n    permissions:\n      id-token: write # Required for requesting the JWT\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Azure login\n      uses: azure/login@v2\n      with:\n        client-id: ${{ secrets.AZURE_CLIENT_ID }}\n        tenant-id: ${{ secrets.AZURE_TENANT_ID }}\n        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}\n\n    - name: Download ${{ inputs.target }} Artifacts\n      uses: actions/download-artifact@v4\n      with:\n        name: ${{ inputs.target }}_Signed\n        path: ./Signed/${{ inputs.target }}\n\n    - name: Archive Previous Release\n      uses: azure/cli@v2\n      with:\n        inlineScript: |\n          account_name=\"${{ secrets.ACCOUNT_NAME }}\"\n          account_key=\"${{ secrets.ACCOUNT_KEY }}\"\n          source_container=\"${{ secrets.WORKER_CONTAINER }}\"\n          destination_container=\"${{ secrets.WORKER_ARCHIVE_CONTAINER }}\"\n          pattern=\"${{ inputs.target }}\"\n\n          # Get a list of blobs in the source container that match the pattern\n          blobs=$(az storage blob list --account-name $account_name --account-key $account_key --container-name $source_container --query \"[?contains(name, '$pattern')].name\" -o tsv)\n\n          # Loop through the blobs and copy each one to the destination container\n          for blob in $blobs\n          do\n            echo \"Copying blob $blob from $source_container to $destination_container\"\n\n            blob=$(echo $blob | tr -d '\\r')\n\n            az storage blob copy start \\\n              --account-name $account_name \\\n              --account-key $account_key \\\n              --destination-container $destination_container \\\n              --destination-blob $blob \\\n              --source-account-name $account_name \\\n              --source-account-key $account_key \\\n              --source-container $source_container \\\n              --source-blob $blob\n\n            echo \"Deleting blob $blob from $source_container\"\n            az storage blob delete \\\n              --account-key $account_key \\\n              --account-name $account_name \\\n              --container-name $source_container \\\n              --name $blob\n          done\n        azcliversion: latest\n\n    - name: Upload Release to Blob Storage\n      uses: azure/cli@v2\n      with:\n        inlineScript: |\n          az storage blob upload-batch --connection-string \"${{ secrets.AZUREBLOBCONNECTIONSTRING }}\" \\\n          -d \"${{ secrets.WORKER_CONTAINER }}\" -s \"./Signed\"\n        azcliversion: latest"
  },
  {
    "path": ".github/workflows/Choco.yml",
    "content": "on:\n  workflow_call:\n\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'Version to deploy'\n        required: true\n\njobs:\n\n  Deploy_Choco:\n    name: Publish App to Chocolatey\n    runs-on: windows-latest\n\n    steps:\n      - uses: actions/download-artifact@v4\n        name: Download Standalone Signed App Artifacts\n        with:\n          name: Standalone_Signed\n          path: .\\StandaloneSigned\n\n      - uses: actions/download-artifact@v4\n        if: ${{ github.event_name == 'push' }}\n        name: Download Build Artifacts\n        with:\n          name: BuildArtifacts\n          path: .\\BuildArtifacts\n\n      - name: Set VERSION Environment Variable (PUSH)\n        if: ${{ github.event_name == 'push' }}\n        run: |\n          $version = Get-Content \".\\BuildArtifacts\\version.txt\"\n          echo \"VERSION=$version\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append\n        shell: powershell\n\n      - name: Set VERSION Environment Variable (Workflow Dispatch)\n        if: ${{ github.event_name == 'workflow_dispatch' }}\n        run: |\n          version = \"${{ inputs.Version }}\"\n          echo \"VERSION=$version\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append\n        shell: powershell\n\n      - uses: actions/download-artifact@v4\n        name: Download Chocolatey Artifacts\n        with:\n          name: Chocolatey\n          path: .\\Chocolatey\n\n      - name: Push to Chocolatey\n        run: |\n          .\\BuildArtifacts\\scripts\\push-choco.ps1 -Version \"${{ env.VERSION}}\" -CHOCOAPIKEY \"${{ secrets.CHOCOAPIKEY}}\"\n        shell: powershell\n\n      - name: Setup tmate session\n        if: ${{ failure() }}\n        uses: mxschmitt/action-tmate@v3\n        timeout-minutes: 15"
  },
  {
    "path": ".github/workflows/Deploy_Desktop.yml",
    "content": "on:\n  push:\n    branches: [ main ]\n    paths-ignore:\n    - '.github/workflows/Deploy_Web.yml'\n    - 'src/PresenceLight.Web/**'\n    - 'src/DockerFiles/**'\n    - '*..md'\n    - 'docs/*..md'\n    - 'Build/**'\n    - 'chocolatey/**'\n\n  pull_request:\n    branches: [ main ]\n    paths-ignore:\n    - '.github/workflows/Deploy_Web.yml'\n    - 'src/PresenceLight.Web/**'\n    - 'src/DockerFiles/**'\n    - '*..md'\n    - 'docs/*..md'\n    - 'Build/**'\n    - 'chocolatey/**'\n\n\njobs:\n\n  Setup_Desktop:\n    name: Setup App for Build\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        ChannelName:\n        - Release\n        - Nightly\n        - Standalone\n\n    env:\n      DOTNET_CLI_TELEMETRY_OPTOUT: 1\n      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1\n      DOTNET_NOLOGO: true\n      BuildConfiguration: Release\n      ACTIONS_ALLOW_UNSECURE_COMMANDS: true\n      Win10RID: net10.0-windows10.0.19041\n\n    steps:\n\n    - name: Checkout Code\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n\n    - name: Use .NET Core SDK 8.0.x and 10.0.x\n      uses: actions/setup-dotnet@v4\n      with:\n        dotnet-version: |\n          8.0.x\n          10.0.x\n\n    - name: Get Version from Nerdbank.GitVersioning\n      uses: dotnet/nbgv@v0.4.2\n      with:\n        setCommonVars: true\n\n    - run: echo \"BuildNumber - ${{ env.GitBuildVersionSimple }}\"\n\n    - name: Add Secrets to appsettings.json\n      run: |\n        ./Build/scripts/update-desktop-settings.ps1 -Release \"${{ matrix.ChannelName}}\" -Version \"${{ env.GitBuildVersionSimple }}\" -ApplicationId \"${{ secrets.ApplicationId }}\" `\n        -ClientSecret \"${{ secrets.ClientSecret }}\" -InstrumentationKey \"${{ secrets.InstrumentationKey }}\" `\n        -LIFXClientId \"${{ secrets.LIFXClientId }}\" -LIFXClientSecret \"${{ secrets.LIFXClientSecret }}\" `\n        -RemoteHueClientId \"${{ secrets.RemoteHueClientId }}\" -RemoteHueClientSecret \"${{ secrets.RemoteHueClientSecret }}\" `\n        -RemoteHueClientAppName \"${{ secrets.RemoteHueClientAppName }}\"\n      shell: pwsh\n      if: ${{ success() && github.event_name != 'pull_request' }}\n\n    - name: Create Version File to Artifact\n      run : |\n        New-Item -Path ./Build -Name \"version.txt\" -ItemType \"file\" -Value \"${{ env.GitBuildVersionSimple }}\"\n      shell: pwsh\n\n    - name: Publish ${{ matrix.ChannelName }} Arifacts\n      uses: actions/upload-artifact@v4\n      with:\n        path: ./src\n        name: PreBuild-${{ matrix.ChannelName }}\n\n    - name: Publish Build Artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        path: Build\n        name: BuildArtifacts\n      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}\n\n    - name: Publish Chocolatey Artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        path: chocolatey\n        name: Chocolatey\n      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}\n\n  Build_WPF:\n    name: Build App\n    needs: Setup_Desktop\n    runs-on: windows-latest\n    strategy:\n      matrix:\n        ChannelName: [ Release,  Nightly,  Standalone ]\n\n    env:\n      DOTNET_CLI_TELEMETRY_OPTOUT: 1\n      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1\n      DOTNET_NOLOGO: true\n      BuildConfiguration: Release\n      ACTIONS_ALLOW_UNSECURE_COMMANDS: true\n      Win10RID: net10.0-windows10.0.19041\n\n    steps:\n\n    - name: setup-msbuild\n      uses: microsoft/setup-msbuild@v1\n\n    - name: Use .NET Core SDK 8.0.x and 10.0.x\n      uses: actions/setup-dotnet@v4\n      with:\n        dotnet-version: |\n          8.0.x\n          10.0.x\n\n    - name: Download PreBuild\n      uses: actions/download-artifact@v4\n      with:\n        name: PreBuild-${{ matrix.ChannelName }}\n        path: ./src\n\n    - name: Download Build Artifacts\n      uses: actions/download-artifact@v4\n      with:\n        name: BuildArtifacts\n        path: ./BuildArtifacts\n\n    - name: Set GitBuildVersionSimple Environment Variable\n      run: |\n        $version = Get-Content \".\\BuildArtifacts\\version.txt\"\n        echo \"GitBuildVersionSimple=$version\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append\n      shell: pwsh\n\n    - name: Create Directory for Channel\n      run: mkdir ./${{ matrix.ChannelName }}\n\n    - name: Update Badge Versions\n      run: |\n        # Update badges\n        [xml]$badge = Get-Content \".\\BuildArtifacts\\ci_badge.svg\"\n        $badge.svg.g[1].text[2].InnerText = \"${{ env.GitBuildVersionSimple }}\"\n        $badge.svg.g[1].text[3].InnerText = \"${{ env.GitBuildVersionSimple }}\"\n        $badge.Save(\".\\${{ matrix.ChannelName }}\\ci_badge.svg\")\n        [xml]$badge = Get-Content \".\\BuildArtifacts\\store_badge.svg\"\n        $badge.svg.g[1].text[2].InnerText = \"${{ env.GitBuildVersionSimple }}\"\n        $badge.svg.g[1].text[3].InnerText = \"${{ env.GitBuildVersionSimple }}\"\n        $badge.Save(\".\\${{ matrix.ChannelName }}\\stable_badge.svg\")\n      shell: powershell\n\n    - name: Setup Windows SDK\n      uses: GuillaumeFalourd/setup-windows10-sdk-action@v2\n      with:\n        sdk-version: 19041\n      if:  ${{ success() && matrix.ChannelName  != 'Standalone' }}\n\n    - name: Build Standalone Presence Light x86\n      run: |\n        dotnet restore .\\src\\DesktopClient\\PresenceLight\\PresenceLight.csproj\n        dotnet publish .\\src\\DesktopClient\\PresenceLight\\PresenceLight.csproj -c ${{ env.BuildConfiguration }} /p:Version=${{ env.GitBuildVersionSimple }} /p:PublishProfile=Properties/PublishProfiles/WinX86.pubxml --property WarningLevel=3\n      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}\n\n    - name: Build Standalone Presence Light x64\n      run: |\n        dotnet restore .\\src\\DesktopClient\\PresenceLight\\PresenceLight.csproj\n        dotnet publish .\\src\\DesktopClient\\PresenceLight\\PresenceLight.csproj -c ${{ env.BuildConfiguration }} /p:Version=${{ env.GitBuildVersionSimple }} /p:PublishProfile=Properties/PublishProfiles/WinX64.pubxml --property WarningLevel=3\n      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}\n\n    - name: Build Standalone Presence Light ARM64\n      run: |\n        dotnet restore .\\src\\DesktopClient\\PresenceLight\\PresenceLight.csproj\n        dotnet publish .\\src\\DesktopClient\\PresenceLight\\PresenceLight.csproj -c ${{ env.BuildConfiguration }} /p:Version=${{ env.GitBuildVersionSimple }} /p:PublishProfile=Properties/PublishProfiles/WinARM64.pubxml --property WarningLevel=3\n      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}\n\n    - name: Zip Standalone PresenceLight x86 Files\n      run: |\n        Compress-Archive -Path '.\\src\\DesktopClient\\PresenceLight\\bin\\${{ env.BuildConfiguration }}\\${{ env.Win10RID }}\\win-x86\\publish\\*' `\n        -DestinationPath \".\\${{ matrix.ChannelName }}\\PresenceLight.${{ env.GitBuildVersionSimple }}-x86.zip\"\n      shell: powershell\n      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}\n\n    - name: Zip Standalone PresenceLight x64 Files\n      run: |\n        Compress-Archive -Path '.\\src\\DesktopClient\\PresenceLight\\bin\\${{ env.BuildConfiguration }}\\${{ env.Win10RID }}\\win-x64\\publish\\*' `\n        -DestinationPath \".\\${{ matrix.ChannelName }}\\PresenceLight.${{ env.GitBuildVersionSimple }}-x64.zip\"\n      shell: powershell\n      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}\n\n    - name: Zip Standalone PresenceLight ARM Files\n      run: |\n        Compress-Archive -Path '.\\src\\DesktopClient\\PresenceLight\\bin\\${{ env.BuildConfiguration }}\\${{ env.Win10RID }}\\win-arm64\\publish\\*' `\n        -DestinationPath \".\\${{ matrix.ChannelName }}\\PresenceLight.${{ env.GitBuildVersionSimple }}-win-arm64.zip\"\n      shell: powershell\n      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}\n\n    - name: Build Appx Package\n      run: |\n        msbuild '.\\src\\DesktopClient\\PresenceLight.Package\\PresenceLight.Package.wapproj' /p:VersionNumber=${{ env.GitBuildVersionSimple }} `\n        /p:ChannelName=${{ matrix.ChannelName }} /p:configuration='${{ env.BuildConfiguration }}' /p:IncludeSymbols=true /p:WarningLevel=3  `\n        /p:AppxPackageDir=\"${{ github.workspace }}\\${{ matrix.ChannelName }}\\\"\n      if:  ${{ success() && matrix.ChannelName  != 'Standalone' }}\n\n    - name: Publish ${{ matrix.ChannelName }} Arifacts\n      uses: actions/upload-artifact@v4\n      with:\n        path: .\\${{ matrix.ChannelName }}\n        name: ${{ matrix.ChannelName }}\n\n    - name: Setup Tmate session\n      if: ${{ failure() }}\n      uses: mxschmitt/action-tmate@v3\n      timeout-minutes: 15\n\n  Code_Signing:\n    name: Code Sign\n    needs: Build_WPF\n    strategy:\n      matrix:\n        target: [ Release, Nightly, Standalone ]\n    uses: isaacrlevin/presencelight/.github/workflows/Sign.yml@main\n    with:\n      target: ${{ matrix.target }}\n    secrets: inherit\n\n  Deploy_Azure_Blob:\n    name: Deploy Nightly App to Azure Blob Storage\n    needs: Code_Signing\n    if: ${{ github.event_name != 'pull_request' }}\n    environment:\n        name: Deploy_Azure_Blob\n        url: ${{ steps.deploy_staging.outputs.webapp-url }}\n    permissions:\n      id-token: write # Required for requesting the JWT\n    runs-on: ubuntu-latest\n    steps:\n    - name: Azure Login\n      uses: azure/login@v2\n      with:\n        client-id: ${{ secrets.AZURE_CLIENT_ID }}\n        tenant-id: ${{ secrets.AZURE_TENANT_ID }}\n        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}\n\n    - name: Download Nightly Signed\n      uses: actions/download-artifact@v4\n      with:\n        name: Nightly_Signed\n        path: \"./NightlySigned\"\n\n    - name: Upload Nightly App to Azure Blob Storage\n      run: |\n        Copy-Item \"./NightlySigned\" -Destination \"./Upload\" -Recurse -Verbose\n        dir .\\Upload\\\n        az storage blob upload --account-key ${{ secrets.ACCOUNT_KEY }} --account-name ${{ secrets.ACCOUNT_NAME }} -f ./Upload/ci_badge.svg -n ci_badge.svg -c nightly --content-type image/svg+xml  --debug --overwrite\n        az storage blob upload --account-key ${{ secrets.ACCOUNT_KEY }} --account-name ${{ secrets.ACCOUNT_NAME }} -f ./Upload/PresenceLight.Package.appinstaller -n PresenceLight.Package.appinstaller -c nightly --content-type application/xml  --debug --overwrite\n        az storage blob upload-batch --account-key ${{ secrets.ACCOUNT_KEY }} --account-name ${{ secrets.ACCOUNT_NAME }} --source ./Upload --pattern *.appxbundle -d nightly --content-type application/vns.ms-appx --debug\n      shell: pwsh\n\n  Deploy_GitHub_Release:\n    name: Deploy App to GitHub Release\n    needs: Deploy_Azure_Blob\n    if: ${{ github.event_name != 'pull_request' }}\n    environment:\n        name: Deploy_GitHub_Release\n        url: ${{ steps.deploy_staging.outputs.webapp-url }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Generate Changelog for Latest Commit\n        id: changelog\n        uses: jaywcjlove/changelog-generator@main\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          filter: ''\n        env:\n          commitMode: true\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Download Standalone Signed App\n        uses: actions/download-artifact@v4\n        with:\n          name: Standalone_Signed\n          path: ./StandaloneSigned\n\n      - name: Download Release Signed App\n        uses: actions/download-artifact@v4\n        with:\n          name: Release_Signed\n          path: ./ReleaseSigned\n\n      - name: Download Build Artifacts\n        uses: actions/download-artifact@v4\n        if: ${{ github.event_name == 'push' }}\n        with:\n          name: BuildArtifacts\n          path: ./BuildArtifacts\n\n      - name: Get Version from Artifact\n        run: |\n          version=$(<\"./BuildArtifacts/version.txt\")\n          echo \"VERSION=$version\" >> $GITHUB_ENV\n\n      - name: Add hashes for Standalone App\n        run: |\n            $zip64Hash = Get-FileHash \"./StandaloneSigned/PresenceLight.${{ env.VERSION }}-x64.zip\" -Algorithm SHA256\n            $zip64Hash.Hash | Out-File -Encoding 'UTF8' \"./StandaloneSigned/PresenceLight.${{ env.VERSION }}-x64.zip.sha256\"\n\n            $zip86Hash = Get-FileHash \"./StandaloneSigned/PresenceLight.${{ env.VERSION }}-x86.zip\" -Algorithm SHA256\n            $zip86Hash.Hash | Out-File -Encoding 'UTF8' \"./StandaloneSigned/PresenceLight.${{ env.VERSION }}-x86.zip.sha256\"\n\n            $zipARMHash = Get-FileHash \"./StandaloneSigned/PresenceLight.${{ env.VERSION }}-win-arm64.zip\" -Algorithm SHA256\n            $zipARMHash.Hash | Out-File -Encoding 'UTF8' \"./StandaloneSigned/PresenceLight.${{ env.VERSION }}-win-arm64.zip.sha256\"\n\n            $appxHash = Get-FileHash \"./ReleaseSigned/PresenceLight.Package_${{ env.VERSION }}.0_Test/PresenceLight.Package_${{ env.VERSION }}.0_x64_x86_ARM64.appxbundle\" -Algorithm SHA256\n            $appxHash.Hash | Out-File -Encoding 'UTF8' \"./ReleaseSigned/PresenceLight.Package_${{ env.VERSION }}.0_x64_x86_ARM64.appxbundle.sha256\"\n        shell: pwsh\n\n      - name: Create GitHub Release\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: \"Desktop-v${{ env.VERSION }}\"\n          body: ${{ steps.changelog.outputs.changelog }}\n          fail_on_unmatched_files: true\n          token: ${{ secrets.GITHUB_TOKEN }}\n          files: |\n            StandaloneSigned/*.zip\n            StandaloneSigned/*.sha256\n            ReleaseSigned/*.sha256\n            ReleaseSigned/**/*.appxbundle\n\n      - name: Setup tmate session\n        if: ${{ failure() }}\n        uses: mxschmitt/action-tmate@v3\n        timeout-minutes: 15\n\n  Deploy_Choco:\n    name: Deploy Standalone App Chocolatey\n    needs: Deploy_GitHub_Release\n    if: ${{ github.event_name != 'pull_request' }}\n    uses: isaacrlevin/presencelight/.github/workflows/Choco.yml@main\n    secrets: inherit\n\n  Deploy_Store:\n    name: Deploy App to Windows Store\n    needs: Deploy_Azure_Blob\n    if: ${{ github.event_name != 'pull_request' }}\n    environment:\n        name: Deploy_Store\n        url: ${{ steps.deploy_staging.outputs.webapp-url }}\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Download Release Signed\n      uses: actions/download-artifact@v4\n      with:\n        name: Release_Signed\n        path: ./ReleaseSigned\n\n    - name: Upload Badges to Azure Blob Storage\n      run: |\n        az storage blob upload --account-key ${{ secrets.ACCOUNT_KEY }} --account-name ${{ secrets.ACCOUNT_NAME }} -f \"./ReleaseSigned/stable_badge.svg\" -n stable_badge.svg -c store --content-type image/svg+xml  --debug --overwrite\n      shell: pwsh\n\n    - name: Windows Store Publish\n      uses: isaacrlevin/windows-store-action@1.0\n      with:\n        tenant-id: ${{ secrets.STORE_TENANT }}\n        client-id: ${{ secrets.STORE_CLIENT_ID }}\n        client-secret: ${{ secrets.STORE_CLIENT_SECRET }}\n        app-id: ${{ secrets.APP_ID }}\n        package-path: \"./ReleaseSigned/\"\n\n  Deploy_Winget:\n    name: Deploy App to WinGet\n    needs: Deploy_GitHub_Release\n    if: ${{ github.event_name != 'pull_request' }}\n    uses: isaacrlevin/presencelight/.github/workflows/WinGet.yml@main\n    secrets: inherit"
  },
  {
    "path": ".github/workflows/Deploy_Web.yml",
    "content": "on:\n\n  push:\n    branches: [ main ]\n    paths-ignore:\n    - '.github/workflows/Deploy_Desktop.yml'\n    - 'src/DesktopClient/**'\n    - 'src/**'\n    - '*..md'\n    - 'docs/*..md'\n    - 'Build/**'\n    - 'chocolatey/**'\n\n  pull_request:\n    branches: [ main ]\n    paths-ignore:\n    - '.github/workflows/Deploy_Desktop.yml'\n    - 'src/DesktopClient/**'\n    - 'src/**'\n    - '*..md'\n    - 'docs/*..md'\n    - 'Build/**'\n    - 'chocolatey/**'\n\njobs:\n  Setup_Web:\n    name: Setup Web\n    runs-on: ubuntu-latest\n\n    env:\n      DOTNET_CLI_TELEMETRY_OPTOUT: 1\n      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1\n      DOTNET_NOLOGO: true\n      BuildConfiguration: Release\n      ACTIONS_ALLOW_UNSECURE_COMMANDS: true\n\n    steps:\n    - name: Checkout Code\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n\n    - name: Use .NET Core SDK 8.0.x and 10.0.x\n      uses: actions/setup-dotnet@v4\n      with:\n        dotnet-version: |\n          8.0.x\n          10.0.x\n\n    - name: Nerdbank.GitVersioning\n      uses: dotnet/nbgv@v0.4.2\n      with:\n        setCommonVars: true\n\n    - run: echo \"BuildNumber - ${{ env.GitBuildVersionSimple }}\"\n\n    - name: Add Secrets to appsettings.json\n      run: |\n        ./Build/scripts/update-web-settings.ps1 -Version \"${{ env.GitBuildVersionSimple }}\" -ApplicationId \"${{ secrets.ApplicationId }}\" `\n        -ClientSecret \"${{ secrets.ClientSecret }}\" -InstrumentationKey \"${{ secrets.InstrumentationKey }}\" `\n        -LIFXClientId \"${{ secrets.LIFXClientId }}\" -LIFXClientSecret \"${{ secrets.LIFXClientSecret }}\" `\n        -RemoteHueClientId \"${{ secrets.RemoteHueClientId }}\" -RemoteHueClientSecret \"${{ secrets.RemoteHueClientSecret }}\" `\n        -RemoteHueClientAppName \"${{ secrets.RemoteHueClientAppName }}\"\n      shell: pwsh\n      if: ${{ success() && github.event_name != 'pull_request' }}\n\n    - name: Create Version File to Artifact\n      run : |\n        New-Item -Path ./Build -Name \"version.txt\" -ItemType \"file\" -Value \"${{ env.GitBuildVersionSimple }}\"\n      shell: pwsh\n\n    - name: Publish PreBuild Arifacts\n      uses: actions/upload-artifact@v4\n      with:\n        path: ./src\n        name: PreBuild\n\n    - name: Publish Files for Build\n      uses: actions/upload-artifact@v4\n      with:\n        path: Build\n        name: BuildArtifacts\n      if:  ${{ success() }}\n\n  Build_Web:\n    name: Build Web\n    needs: Setup_Web\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        include:\n          - ChannelName: Windows_x64_x86\n            RID: win-x64\n          - ChannelName: Windows_ARM\n            RID: win-arm64\n          - ChannelName: macOS\n            RID: osx-x64\n          - ChannelName: Linux_ARM\n            RID: linux-arm\n          - ChannelName: Linux_ARM64\n            RID: linux-x64\n          - ChannelName: Linux_Musl_x64\n            RID: linux-musl-x64\n          - ChannelName: Linux_Musl_ARM_x64\n            RID: linux-musl-arm64\n\n    env:\n      DOTNET_CLI_TELEMETRY_OPTOUT: 1\n      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1\n      DOTNET_NOLOGO: true\n      BuildConfiguration: Release\n      ACTIONS_ALLOW_UNSECURE_COMMANDS: true\n\n    steps:\n\n    - name: Use .NET Core SDK 8.0.x and 10.0.x\n      uses: actions/setup-dotnet@v4\n      with:\n        dotnet-version: |\n          8.0.x\n          10.0.x\n\n    - name: Download PreBuild\n      uses: actions/download-artifact@v4\n      with:\n        name: PreBuild\n        path: ./src\n\n    - name: Download Build Artifacts\n      uses: actions/download-artifact@v4\n      with:\n        name: BuildArtifacts\n        path: ./BuildArtifacts\n\n    - name: Get Version from Artifact\n      run: |\n        version=$(<\"${{ github.workspace }}/BuildArtifacts/version.txt\")\n        echo \"GitBuildVersionSimple=$version\" >> $GITHUB_ENV\n\n    - name: Create Directory for ${{ matrix.ChannelName }} Channel\n      run: mkdir ./${{ matrix.ChannelName }}\n\n    - name: dotnet publish ${{ matrix.RID }}\n      run: dotnet publish './src/PresenceLight.Web/PresenceLight.Web.csproj' -r ${{ matrix.RID }} -c ${{ env.BuildConfiguration }} /p:PublishSingleFile=true -o ./PresenceLight.${{ env.GitBuildVersionSimple }}_${{ matrix.ChannelName }} /p:Version=${{ env.GitBuildVersionSimple }} --property WarningLevel=0\n      if: ${{ success() }}\n\n    - name: Zip PresenceLight Web Files\n      run: |\n        Compress-Archive -Path './PresenceLight.${{ env.GitBuildVersionSimple }}_${{ matrix.ChannelName }}' `\n        -DestinationPath ./${{ matrix.ChannelName }}/PresenceLight.${{ matrix.ChannelName }}.${{ env.GitBuildVersionSimple }}.zip\n      shell: pwsh\n\n    - name: Publish ${{ matrix.ChannelName }} Arifacts\n      uses: actions/upload-artifact@v4\n      with:\n        path: ./${{ matrix.ChannelName }}\n        name: ${{ matrix.ChannelName }}\n\n  Code_Signing:\n    name: Code Sign Worker\n    needs: Build_Web\n    strategy:\n      matrix:\n        target: [ Windows_x64_x86, Windows_ARM, macOS, Linux_ARM, Linux_ARM64, Linux_Musl_x64, Linux_Musl_ARM_x64 ]\n    uses: isaacrlevin/presencelight/.github/workflows/Sign.yml@main\n    with:\n      target: ${{ matrix.target }}\n    secrets: inherit\n\n  Deploy_Azure_Blob:\n    needs: Code_Signing\n    name: Deploy Worker to Azure Blob Storage\n    if: ${{ github.event_name != 'pull_request' }}\n    strategy:\n      matrix:\n        target: [ Windows_x64_x86, Windows_ARM, macOS, Linux_ARM, Linux_ARM64, Linux_Musl_x64, Linux_Musl_ARM_x64 ]\n    uses: isaacrlevin/presencelight/.github/workflows/Azure_Blob_Deploy.yml@main\n    with:\n      target: ${{ matrix.target }}\n    secrets: inherit\n\n  Deploy_Containers:\n    name: Deploy Web Containers (DockerHub / GitHub Packages)\n    needs: Build_Web\n    if: ${{ github.event_name != 'pull_request' }}\n    environment:\n        name: Deploy_Containers\n        url: ${{ steps.deploy_staging.outputs.webapp-url }}\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Download PreBuild\n        uses: actions/download-artifact@v4\n        with:\n          name: PreBuild\n          path: ./src\n\n      - name: Download Build Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: BuildArtifacts\n          path: ./BuildArtifacts\n\n      - name: Get Version from Artifact\n        run: |\n          version=$(<\"./BuildArtifacts/version.txt\")\n          echo \"VERSION=$version\" >> $GITHUB_ENV\n\n      - name: Update Docker Files\n        run: |\n          $dockerFileLatest = Get-Content -path \"./src/PresenceLight.Web/Dockerfile\" -Raw\n          $dockerFileLatest = $dockerFileLatest -replace '{VERSION}', \"${{ env.VERSION }} \"\n          $dockerFileLatest | Set-Content -Path \"./src/PresenceLight.Web/Dockerfile\"\n\n          $dockerFile32 = Get-Content -path \"./src/PresenceLight.Web/Dockerfile.debian-arm32\" -Raw\n          $dockerFile32 = $dockerFile32 -replace '{VERSION}', \"${{ env.VERSION }} \"\n          $dockerFile32 | Set-Content -Path \"./src/PresenceLight.Web/Dockerfile.debian-arm32\"\n\n          $dockerFile64 = Get-Content -path \"./src/PresenceLight.Web/Dockerfile.debian-arm64\" -Raw\n          $dockerFile64 = $dockerFile64 -replace '{VERSION}', \"${{ env.VERSION }} \"\n          $dockerFile64 | Set-Content -Path \"./src/PresenceLight.Web/Dockerfile.debian-arm64\"\n        shell: pwsh\n        if: ${{ success() && github.event_name != 'pull_request' }}\n\n      - name: Push latest Container tag to GitHub Registry\n        uses: opspresso/action-docker@master\n        with:\n          args: --docker\n        env:\n          USERNAME: isaacrlevin\n          REGISTRY: \"ghcr.io\"\n          PASSWORD: ${{ secrets.GH_PERSONAL_TOKEN }}\n          DOCKERFILE: \"./src/PresenceLight.Web/Dockerfile\"\n          IMAGE_NAME: \"isaacrlevin/presencelight\"\n          TAG_NAME: \"${{ env.VERSION }}\"\n          LATEST: \"true\"\n          BUILD_PATH: \"./src/\"\n\n      - name: Push ARM Container tag to GitHub Registry\n        uses: opspresso/action-docker@master\n        with:\n          args: --docker\n        env:\n          USERNAME: isaacrlevin\n          REGISTRY: \"ghcr.io\"\n          PASSWORD: ${{ secrets.GH_PERSONAL_TOKEN }}\n          DOCKERFILE: \"./src/PresenceLight.Web/Dockerfile.debian-arm32\"\n          IMAGE_NAME: \"isaacrlevin/presencelight\"\n          TAG_NAME: \"debian-arm32\"\n          BUILD_PATH: \"./src/\"\n\n      - name: Push ARM64 tag to GitHub Registry\n        uses: opspresso/action-docker@master\n        with:\n          args: --docker\n        env:\n          USERNAME: isaacrlevin\n          REGISTRY: \"ghcr.io\"\n          PASSWORD: ${{ secrets.GH_PERSONAL_TOKEN }}\n          DOCKERFILE: \"./src/PresenceLight.Web/Dockerfile.debian-arm64\"\n          IMAGE_NAME: \"isaacrlevin/presencelight\"\n          TAG_NAME: \"debian-arm64\"\n          BUILD_PATH: \"./src/\"\n\n      - name: Push ARM tagto DockerHub (Versioned)\n        uses: opspresso/action-docker@master\n        with:\n          args: --docker\n        env:\n          USERNAME: ${{ secrets.DOCKER_USERNAME }}\n          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}\n          DOCKERFILE: \"./src/PresenceLight.Web/Dockerfile.debian-arm32\"\n          IMAGE_NAME: \"isaaclevin/presencelight\"\n          TAG_NAME: \"${{ env.VERSION }}-debian-arm32\"\n          BUILD_PATH: \"./src/\"\n\n      - name: Push ARM tag to DockerHub (Latest)\n        uses: opspresso/action-docker@master\n        with:\n          args: --docker\n        env:\n          USERNAME: ${{ secrets.DOCKER_USERNAME }}\n          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}\n          DOCKERFILE: \"./src/PresenceLight.Web/Dockerfile.debian-arm32\"\n          IMAGE_NAME: \"isaaclevin/presencelight\"\n          TAG_NAME: \"debian-arm32\"\n          BUILD_PATH: \"./src/\"\n\n      - name: Push ARM64 tag to DockerHub (Versioned)\n        uses: opspresso/action-docker@master\n        with:\n          args: --docker\n        env:\n          USERNAME: ${{ secrets.DOCKER_USERNAME }}\n          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}\n          DOCKERFILE: \"./src/PresenceLight.Web/Dockerfile.debian-arm64\"\n          IMAGE_NAME: \"isaaclevin/presencelight\"\n          TAG_NAME: \"${{ env.VERSION }}-debian-arm64\"\n          BUILD_PATH: \"./src/\"\n\n      - name: Push ARM64 tag to DockerHub (Latest)\n        uses: opspresso/action-docker@master\n        with:\n          args: --docker\n        env:\n          USERNAME: ${{ secrets.DOCKER_USERNAME }}\n          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}\n          DOCKERFILE: \"./src/PresenceLight.Web/Dockerfile.debian-arm64\"\n          IMAGE_NAME: \"isaaclevin/presencelight\"\n          TAG_NAME: \"debian-arm64\"\n          BUILD_PATH: \"./src/\"\n\n      - name: Push latest tag to DockerHub\n        uses: opspresso/action-docker@master\n        with:\n          args: --docker\n        env:\n          USERNAME: ${{ secrets.DOCKER_USERNAME }}\n          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}\n          DOCKERFILE: \"./src/PresenceLight.Web/Dockerfile\"\n          IMAGE_NAME: \"isaaclevin/presencelight\"\n          TAG_NAME: \"${{ env.VERSION }}\"\n          LATEST: \"true\"\n          BUILD_PATH: \"./src/\"\n\n  Deploy_GitHub_Release:\n    name: Deploy Web to GitHub Release\n    needs: Deploy_Azure_Blob\n    if: ${{ github.event_name != 'pull_request' }}\n    environment:\n        name: Deploy_GitHub_Release\n        url: ${{ steps.deploy_staging.outputs.webapp-url }}\n    runs-on: ubuntu-latest\n    steps:\n\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Generate Changelog\n        id: changelog\n        uses: jaywcjlove/changelog-generator@main\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          filter: ''\n        env:\n          commitMode: true\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Download Windows_x64_x86 Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: Windows_x64_x86\n          path: ./Sign/Windows_x64_x86\n\n      - name: Download Windows_ARM Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: Windows_ARM\n          path: ./Sign/Windows_ARM\n\n      - name: Download macOS Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: macOS\n          path: ./Sign/macOS\n\n      - name: Download Linux_ARM Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: Linux_ARM\n          path: ./Sign/Linux_ARM\n\n      - name: Download Linux_ARM64 Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: Linux_ARM64\n          path: ./Sign/Linux_ARM64\n\n      - name: Download Linux_Musl_x64 Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: Linux_Musl_x64\n          path: ./Sign/Linux_Musl_x64\n\n      - name: Download Linux_Musl_ARM_x64 Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: Linux_Musl_ARM_x64\n          path: ./Sign/Linux_Musl_ARM_x64\n\n      - name: Download Build Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: BuildArtifacts\n          path: \"./BuildArtifacts\"\n\n      - name: Get Version from Artifact\n        run: |\n          version=$(<\"./BuildArtifacts/version.txt\")\n          echo \"VERSION=$version\" >> $GITHUB_ENV\n\n      - name: Create GitHub Release\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: \"Web-v${{ env.VERSION }}\"\n          body: ${{ steps.changelog.outputs.changelog }}\n          fail_on_unmatched_files: true\n          token: ${{ secrets.GITHUB_TOKEN }}\n          files: |\n            ./Sign/**/*.zip\n\n      - name: Setup Tmate session\n        if: ${{ failure() }}\n        uses: mxschmitt/action-tmate@v3\n        timeout-minutes: 15\n"
  },
  {
    "path": ".github/workflows/Sign.yml",
    "content": "name: Code Sign App\n\non:\n  workflow_dispatch:\n    inputs:\n      target:\n        description: 'Target of Channel Name to Sign'\n        required: true\n        default: ''\n        type: string\n\n  workflow_call:\n    inputs:\n      target:\n        description: 'Target of Channel Name to Sign'\n        required: true\n        default: ''\n        type: string\n\njobs:\n\n  Sign_Code:\n    name: Sign ${{ inputs.target }} App\n    permissions:\n      id-token: write # Required for requesting the JWT\n    runs-on: windows-latest\n\n    steps:\n    - name: Download ${{ inputs.target }} Artifacts\n      uses: actions/download-artifact@v4\n      with:\n        name: ${{ inputs.target }}\n        path: .\\ToSign\\${{ inputs.target }}\n\n    - name: Download Build Artifacts\n      uses: actions/download-artifact@v4\n      with:\n        name: BuildArtifacts\n        path: .\\BuildArtifacts\n\n    - name: Setup .NET\n      uses: actions/setup-dotnet@v4\n      with:\n        dotnet-version: |\n          8.0.x\n          10.0.x\n\n    - name: Install Code Sign CLI tool\n      # run: dotnet tool install --tool-path . sign --version 0.9.0-beta.23063.3\n      run: dotnet tool install --tool-path . --prerelease sign\n\n    - name: Azure Login\n      uses: azure/login@v2\n      with:\n        client-id: ${{ secrets.AZURE_CLIENT_ID }}\n        tenant-id: ${{ secrets.AZURE_TENANT_ID }}\n        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}\n\n    - name: Run Code Signing CLI\n      shell: pwsh\n      run: >\n        ./sign code azure-key-vault\n        '**/*.{exe,zip,appxbundle,appinstaller}'\n        --timestamp-url \"http://timestamp.digicert.com\"\n        --base-directory \"${{ github.workspace }}\\ToSign\"\n        --file-list \"${{ github.workspace }}\\BuildArtifacts\\Signing\\filelist.txt\"\n        --publisher-name \"Isaac Levin\"\n        --description \"PresenceLight\"\n        --description-url \"https://github.com/isaacrlevin/presencelight\"\n        --azure-key-vault-managed-identity true\n        --azure-key-vault-url \"${{ secrets.KEY_VAULT_URL }}\"\n        --azure-key-vault-certificate \"${{ secrets.KEY_VAULT_CERTIFICATE_ID }}\"\n        --verbosity Trace\n\n    - name: Publish Signed ${{ inputs.target }} Packages\n      uses: actions/upload-artifact@v4\n      with:\n        path: .\\ToSign\\${{ inputs.target }}\n        name: '${{ inputs.target }}_Signed'\n\n    - name: Setup Tmate session\n      if: ${{ failure() }}\n      uses: mxschmitt/action-tmate@v3\n      timeout-minutes: 15"
  },
  {
    "path": ".github/workflows/WinGet.yml",
    "content": "name: Winget Publish\non:\n  workflow_dispatch:\n    inputs:\n      Version:\n        description: 'Release'\n        required: true\n        default: '5.0'\n        type: string\n\n  workflow_call:\n\njobs:\n  publish:\n    runs-on: windows-latest\n    name: Publish App to Winget\n    env:\n      WINGETCREATE_TOKEN: ${{ secrets.WINGETCREATE_TOKEN }}\n    steps:\n\n      - name: Download Artifacts for Winget Publish\n        uses: actions/download-artifact@v4\n        if: ${{ github.event_name == 'push' }}\n        with:\n          name: BuildArtifacts\n          path: .\\BuildArtifacts\n\n      - name: Set VERSION Environment Variable (PUSH)\n        if: ${{ github.event_name == 'push' }}\n        run: |\n          $version = Get-Content \".\\BuildArtifacts\\version.txt\"\n          echo \"VERSION=$version\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append\n        shell: pwsh\n\n      - name: Set VERSION Environment Variable (Workflow Dispatch)\n        if: ${{ github.event_name == 'workflow_dispatch' }}\n        run: |\n          version = \"${{ inputs.Version }}\"\n          echo \"VERSION=$version\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append\n        shell: pwsh\n\n      - name: Publish App to Winget\n        working-directory: ${{ github.workspace }}\\BuildArtifacts\\scripts\n        run: |\n          .\\push-winget.ps1 -Version \"${{ env.VERSION }}\" -Token \"${{ secrets.WINGETCREATE_TOKEN }}\"\n        shell: pwsh\n\n      - name: Setup Tmate session\n        if: ${{ failure() }}\n        uses: mxschmitt/action-tmate@v3\n        timeout-minutes: 15\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# Created by https://www.gitignore.io/api/visualstudio,windows\n# Edit at https://www.gitignore.io/?templates=visualstudio,windows\n\n### Windows ###\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n### VisualStudio ###\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nwin-arm64/\nNightly/\n.Store\nSignClient.exe\n\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n.vscode/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n#*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# End of https://www.gitignore.io/api/visualstudio,windows\n\n/src/lib/RCWPF/2020.1.218.45.NoXaml.Trial\n/sign.ps1\n/src/DesktopClient/PresenceLight/appsettings.Development.json\n/src/PresenceLight.Web/PresenceLightSettings.Development.json\n/src/PresenceLight.Web/appsettings.Development.json\nsrc/local-action-test-wpf.ps1\n.github/workflows/test.yml\nBuild/StorePublish/.env.local\nBuild/StorePublish/node_modules\nBuild/StorePublish/temp.zip\n/src/DesktopClient/PresenceLight/Properties/launchSettings.json\n/src/PresenceLight.Web/config/\nlogs/\n/src/config\n\n.ionide\nsrc/DesktopClient/PresenceLight/settings.json\nsettings.json\n"
  },
  {
    "path": "Build/Signing/filelist.txt",
    "content": "**/PresenceLight.*\n\n"
  },
  {
    "path": "Build/Worker/presencelight.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIICBTCCAasCFBvttCTh9n9rqOSzRE1jFBdeRyRXMAoGCCqGSM49BAMCMIGEMQsw\nCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjEUMBIGA1UEBwwLV29vZGlu\ndmlsbGUxFDASBgNVBAoMC0lzYWFjIExldmluMRYwFAYDVQQDDA1wcmVzZW5jZWxp\nZ2h0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tMB4XDTIwMDUyNDIxNTAz\nMloXDTIxMDUyNDIxNTAzMlowgYQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNo\naW5ndG9uMRQwEgYDVQQHDAtXb29kaW52aWxsZTEUMBIGA1UECgwLSXNhYWMgTGV2\naW4xFjAUBgNVBAMMDXByZXNlbmNlbGlnaHQxHDAaBgkqhkiG9w0BCQEWDXRlc3RA\ndGVzdC5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQmWh86vQxjFjlNHt/6\n8PD9Jg2TJIvaceGaiq+2t/CKoG0FEeiHUYwiozztU6Ad5+dp25OSUzqz2JFy/N+J\niI+2MAoGCCqGSM49BAMCA0gAMEUCIFnvmXTnJQNmPCS6QDVYLrFIYTx3Gzi1nTVD\nh5n//+/7AiEAlUxK5U45oJtK2CuHcwquxzar8eB9ZcBAydfuGA9G7o8=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "Build/Worker/presencelight.service",
    "content": "[Unit]\nDescription=PresenceLight is a solution to broadcast your various statuses to a Philips Hue or LIFX light bulb.\n\n[Service]\nWorkingDirectory=/home/pi/PresenceLight\nExecStart=/home/pi/PresenceLight/PresenceLight\nRestart=always\n# Restart service after 10 seconds if the dotnet service crashes:\nRestartSec=10\nKillSignal=SIGINT\nSyslogIdentifier=PresenceLight\nUser=pi\nEnvironment=ASPNETCORE_ENVIRONMENT=Production\nEnvironment=DOTNET_PRINT_TELEMETRY_MESSAGE=false\n\n[Install]\nWantedBy=multi-user.target"
  },
  {
    "path": "Build/Worker/trust-cert.sh",
    "content": "pk12util -d sql:$HOME/.pki/nssdb -i presencelight.pfx\n\ncertutil -d sql:$HOME/.pki/nssdb -A -t \"P,,\" -n 'presencelight' -i presencelight.crt"
  },
  {
    "path": "Build/scripts/push-choco.ps1",
    "content": "Param\n(\n    [parameter(Mandatory = $true)]    [string]\n    $Version,\n    [parameter(Mandatory = $true)]    [string]\n    $CHOCOAPIKEY\n)\n\nfunction Hash-Files {\n    param (\n        [parameter(Mandatory = $true)]\n        [string]\n        $Version\n    )\n\n    # Get the latest release from GitHub\n    $github = Invoke-RestMethod -uri \"https://api.github.com/repos/isaacrlevin/presencelight/releases\"\n\n    $targetRelease = $github | Where-Object -Property name -match \"Desktop-v$Version\" | Select-Object -First 1\n\n    mkdir .\\Download\n    $fileNames = (, \"x86\", \"x64\", \"win-arm64\")\n\n    # Get the Hashes from the GitHub Release and save them to files\n    $hashUrls = $targetRelease | Select-Object -ExpandProperty assets -First 1 | Where-Object -Property name -match '.*?.zip.sha256' | Select-Object -ExpandProperty browser_download_url\n\n    foreach ($url in $hashUrls) {\n        foreach ($fileName in $fileNames) {\n            if ($url -like \"*$fileName*\") {\n                $filePath = \".\\Download\\$fileName.zip.sha256\"\n                Invoke-WebRequest -Uri $url -OutFile $filePath\n                break\n            }\n        }\n    }\n\n    $hash86 = get-content \".\\Download\\x86.zip.sha256\"\n    $hash64 = get-content \".\\Download\\x64.zip.sha256\"\n    $hashARM = get-content \".\\Download\\win-arm64.zip.sha256\"\n\n    # Update ChocolateyInstall.ps1 with Hashes\n    $installFile = Get-Content -path \".\\Chocolatey\\tools\\ChocolateyInstall.ps1\" -Raw\n    $installFile = $installFile -replace '{ReplaceCheckSumARM}', $hashARM\n    $installFile = $installFile -replace '{ReplaceCheckSumx86}', $hash86\n    $installFile = $installFile -replace '{ReplaceCheckSumx64}', $hash64\n\n    # Update Verification.txt with Hashes\n    $verificationFile = Get-Content -path \".\\Chocolatey\\tools\\Verification.txt\"\n    $verificationFile = $verificationFile -replace '{HASHx64}', $hash64\n    $verificationFile = $verificationFile -replace '{HASHx86}', $hash86\n    $verificationFile = $verificationFile -replace '{HASHARM}', $hashARM\n\n    # Get the Download Urls for the Zip files and update ChocolateyInstall.ps1 and Verification.txt with Urls\n    $zipUrls = $targetRelease | Select-Object -ExpandProperty assets | Where-Object { $_.name -like '*.zip' } | Select-Object -ExpandProperty browser_download_url\n\n    foreach ($url in $zipUrls) {\n        if ($url -like \"*x64*\") {\n            $installFile = $installFile -replace '{x64Link}' , $url\n            $verificationFile = $verificationFile -replace '{x64Link}' , $url\n        }\n        if ($url -like \"*x86*\") {\n            $installFile = $installFile -replace '{x86Link}' , $url\n            $verificationFile = $verificationFile -replace '{x86Link}' , $url\n        }\n        if ($url -like \"*arm64*\") {\n            $installFile = $installFile -replace '{ARMLink}' , $url\n            $verificationFile = $verificationFile -replace '{ARMLink}' , $url\n        }\n    }\n\n    # Save the updated files\n    $verificationFile | Set-Content -Path \".\\Chocolatey\\tools\\Verification.txt\"\n    $installFile | Set-Content -Path \".\\Chocolatey\\tools\\ChocolateyInstall.ps1\"\n}\n\n\nHash-Files -Version $Version\n\n# Chocolatey Pack\n& choco.exe pack \".\\Chocolatey\\PresenceLight.nuspec\" --version \"${Version}\" --OutputDirectory \".\\Chocolatey\"\n\n$CHOCOAPIKEY = $CHOCOAPIKEY -replace \"`n\", \"\" -replace \"`r\", \"\" -replace \" \", \"\"\n\n& choco.exe apikey --key \"${CHOCOAPIKEY}\" --source https://push.chocolatey.org/\n\n$nupkgs = gci \".\\Chocolatey\\PresenceLight.*.nupkg\" | Select -ExpandProperty FullName\nforeach ($nupkg in $nupkgs) {\n    & choco.exe push $nupkg --source https://push.chocolatey.org/ --debug --verbose\n}"
  },
  {
    "path": "Build/scripts/push-winget.ps1",
    "content": "\nParam\n(\n    [parameter(Mandatory = $true)]\n    [string]\n    $Version,\n    [parameter(Mandatory = $false)]\n    [string]\n    $Token\n)\n\n$github = Invoke-RestMethod -uri \"https://api.github.com/repos/isaacrlevin/presencelight/releases\"\n$targetRelease = $github | Where-Object -Property name -match \"Desktop-v$Version\" | Select-Object -First 1\n\n$installerUrl = $targetRelease | Select-Object -ExpandProperty assets | Where-Object { $_.name -like '*.appxbundle' } | Select-Object -ExpandProperty browser_download_url\n# Update package using wingetcreate\nInvoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe\n.\\wingetcreate.exe update \"isaaclevin.presencelight\" --version $Version --urls \"$installerUrl\" --submit --token $Token"
  },
  {
    "path": "Build/scripts/update-desktop-settings.ps1",
    "content": "Param\n(\n    [parameter(Mandatory = $true)]    [string]\n    $Release,\n\n    [parameter(Mandatory = $true)]    [string]\n    $Version,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $ApplicationId,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $ClientSecret,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $InstrumentationKey,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $LIFXClientId,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $LIFXClientSecret,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $RemoteHueClientId,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $RemoteHueClientSecret,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $RemoteHueClientAppName\n)\n\n\nswitch ($Release) {\n    \"Release\" {\n        Write-Host \"Updating AppxManifest for Release\"\n        $xmlPath= Resolve-Path \".\\src\\DesktopClient\\PresenceLight.Package\\Package.appxmanifest\"\n        Write-Host \"Updating AppxManifest for Release\"\n        [xml]$manifest = get-content  $xmlPath\n        $manifest.Package.Identity.Version = \"${Version}.0\"\n        $manifest.save($xmlPath)\n    }\n    \"Nightly\" {\n        Write-Host \"Updating AppxManifest for Nightly\"\n        $xmlPath= Resolve-Path \".\\src\\DesktopClient\\PresenceLight.Package\\Package-Nightly.appxmanifest\"\n        Write-Host \"Updating AppxManifest for Nightly\"\n        [xml]$manifest = get-content  $xmlPath\n        $manifest.Package.Identity.Version = \"${Version}.0\"\n        $manifest.save($xmlPath)\n    }\n    \"Standalone\" {\n    }\n}\n\n\nWrite-Host \"Updating AppSettings for All Channels\"\n$appsettings = get-content \".\\src\\DesktopClient\\PresenceLight\\appsettings.json\" -raw | ConvertFrom-Json\n$appsettings.aadSettings.clientId = \"${ApplicationId}\"\n$appsettings.appVersion = \"${Version}\"\n$appsettings.lightSettings.lifx.LIFXClientId = \"${LIFXClientId}\"\n$appsettings.lightSettings.lifx.LIFXClientSecret = \"${LIFXClientSecret}\"\n$appsettings.applicationInsights.instrumentationkey = \"${InstrumentationKey}\"\n$appsettings.lightSettings.hue.RemoteHueClientId = \"${RemoteHueClientId}\"\n$appsettings.lightSettings.hue.RemoteHueClientSecret = \"${RemoteHueClientSecret}\"\n$appsettings.lightSettings.hue.RemoteHueClientAppName = \"${RemoteHueClientAppName}\"\n$appsettings | ConvertTo-Json -depth 32 | set-content '.\\src\\DesktopClient\\PresenceLight\\appsettings.json'"
  },
  {
    "path": "Build/scripts/update-web-settings.ps1",
    "content": "Param\n(\n    [parameter(Mandatory = $true)]    [string]\n    $Version,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $ApplicationId,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $ClientSecret,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $InstrumentationKey,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $LIFXClientId,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $LIFXClientSecret,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $RemoteHueClientId,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $RemoteHueClientSecret,\n\n    [parameter(Mandatory = $true)]\n    [string]\n    $RemoteHueClientAppName\n)\n\n# Update AppSettings.json. This must be done before build.\n$appsettings = get-content \".\\src\\PresenceLight.Web\\appsettings.json\" -raw | ConvertFrom-Json\n\n$appsettings.AADSettings.clientId = $ApplicationId\n$appsettings.AADSettings.clientSecret = $ClientSecret\n\n$appsettings.appVersion = $GitBuildVersionSimple\n\n$appsettings.applicationInsights.instrumentationkey = $InstrumentationKey\n$appsettings | ConvertTo-Json -depth 32 | set-content '.\\src\\PresenceLight.Web\\appsettings.json'\n\n# Update PresenceLightSettings.json. This must be done before build.\n$PresenceLightSettings = get-content \".\\src\\PresenceLight.Web\\PresenceLightSettings.json\" -raw | ConvertFrom-Json\n\n$PresenceLightSettings.lightSettings.lifx.LIFXClientId = $LIFXClientId\n$PresenceLightSettings.lightSettings.lifx.LIFXClientSecret = $LIFXClientSecret\n\n$PresenceLightSettings.lightSettings.hue.RemoteHueClientId = $RemoteHueClientId\n$PresenceLightSettings.lightSettings.hue.RemoteHueClientSecret = $RemoteHueClientSecret\n$PresenceLightSettings.lightSettings.hue.RemoteHueClientAppName = $RemoteHueClientAppName\n\n$PresenceLightSettings | ConvertTo-Json -depth 32 | set-content '.\\src\\PresenceLight.Web\\PresenceLightSettings.json'"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Isaac Levin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "PrivacyPolicy.md",
    "content": "# Information Collected And Transmitted By PresenceLight\n\nFirst, a reminder: PresenceLight is provided \"as is\", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement.\n\nWith that out of the way, here's a breakdown of all the information we may collect via Application Insights.\n\n### Application-Level\nIncludes:\n* Exception information\n  * Could, in rare cases, contain paths packages on your computer\n* Machine name\n* Host name\n* Version number (e.g. 2.0.x.x)\n\n### Operating System-Level\nIncludes:\n* Architecture (e.g. 32-bit)\n* Version (e.g. Windows 10.0.17763.0)\n* Build (e.g. 17134.1.amd64fre.rs4_release.180410-1804)\n* Available processors/cores (e.g. 8 cores)\n* Machine Name (e.g. MyFastPC)\n* .NET Core Common Language Runtime version (e.g. 4.0.30319.42000)\n\n## Package Sources\n\n**OS information and IP address**\n\nWhen Presence Light makes calls to authenticate users, it uses the Microsoft Graph Api. The author of PresenceLight does not have access to this information, but Microsoft Graph does and logs this information. \n**3rd-party package source**\n\nWhen user specifies a different package source than the default source at http://nuget.org, he/she will be subjected to the privacy policy of that website. NuGet Package Explorer does not send any such data to its author.\n\n## Third-Party Policies\n\n* LIFX https://www.lifx.com/pages/privacy-policy\n* Philips Hue https://www2.meethue.com/en-us/support/privacy-policy\n* Microsoft Store https://docs.microsoft.com/en-us/legal/windows/agreements/store-policies\n"
  },
  {
    "path": "README.md",
    "content": "![Logo](https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png)\n# PresenceLight\n\n### NOTE: Due to internal changes at Microsoft, the Web/Container Version no longer works. I am currently looking into resolving this issue, but in the meantime, you will have to create an App Registration yourself and build the code on your own. :(\n\n\n![.github/workflows/Deploy_Web.yml](https://github.com/isaacrlevin/presencelight/workflows/.github/workflows/Deploy_Web.yml/badge.svg)\n![.github/workflows/Deploy_Desktop.yml](https://github.com/isaacrlevin/presencelight/workflows/.github/workflows/Deploy_Desktop.yml/badge.svg)\n\n## Get PresenceLight\n\n### Desktop Version\n\n| Nightly | Microsoft Store | Chocolatey | GitHub Releases |\n| ------- | --------------- | ---------- | --------------- |\n| [<img src=\"https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png\" width=\"100\">](https://presencelight.blob.core.windows.net/nightly/index.html)| [<img src=\"https://github.com/isaacrlevin/PresenceLight/raw/main/static/store.svg\" width=\"100\">](https://www.microsoft.com/en-us/p/presencelight/9nffkd8gznl7) | [<img src=\"https://chocolatey.org/assets/images/global-shared/logo.svg\" width=\"100\">](https://chocolatey.org/packages/PresenceLight/) | [<img src=\"https://user-images.githubusercontent.com/8878502/110871471-55fe7c00-8283-11eb-8ce4-afeeaf62458a.png\" width=\"100\">](https://github.com/isaacrlevin/presencelight/releases) |\n\n## Web Version\n\n|Web Download Site | Web Container from DockerHub | Web Container from GitHub Registry\n| ------- | --------------- | --------------- |\n[<img src=\"https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png\" width=\"100\">](https://presencelightapp.azurewebsites.net/) | [<img src=\"https://user-images.githubusercontent.com/8878502/110870857-2602a900-8282-11eb-8846-89c61a219236.png\" width=\"100\">](https://hub.docker.com/r/isaaclevin/presencelight) | [<img src=\"https://user-images.githubusercontent.com/8878502/110871471-55fe7c00-8283-11eb-8ce4-afeeaf62458a.png\" width=\"100\">](https://github.com/users/isaacrlevin/packages/container/package/presencelight) |\n\n## App Versions\n\n| Application Type |  Platforms | Readme\n|--- |  ---- | ---- |\n| Desktop (.NET 10) | Windows 10 (min Version 1803) / Windows 11 | [Desktop Readme](docs/desktop-README.md)\n| Web (ASP.NET 10) | Windows, MacOS, Linux (Debian, AMD x64, ARM, ARM x64),  | [Web Readme](docs/web-README.md)\n## What is PresenceLight?\n\n[PresenceLight](https://isaacl.dev/presence-light) is a solution to broadcast your various statuses to various kinds of smart lights. Some statuses you can broadcast are: your availability in Microsoft Teams or color of your choosing. There are other solutions that do something similar to sending Teams Availability to a light, but they require a tethered solution (plugging a light into a computer via USB). What PresenceLight does is leverage the [Presence Api](https://docs.microsoft.com/graph/api/presence-get), which is available in [Microsoft Graph](https://docs.microsoft.com/graph/overview), allowing to retrieve your presence without having to be tethered. This could potentially allow someone to update the light bulb from a remote machine they do not use.\n\n#### [Blog Post](https://isaacl.dev/presence-light)\n\n#### [PresenceLight Demos](https://www.youtube.com/playlist?list=PL_IEvQa-oTVtB3fKUclJNNJ1r-Sxtjc-m)\n\n## Supported Hardware\n\n| Light Type  |\n| ------------ |\n| Philips Hue (Local and Remote)\n| LIFX |\n| Yeelight |\n| Philips Wiz |\n| [WLED](https://kno.wled.ge/) (via serial or web API) |\n| Any light which can be controlled via a GET or POST call to a web API |\n\n## Docs\n- [Configure Hardware](docs/configure-hardware.md)\n- [FAQ](docs/faq.mdFAQ)\n- [Configure Custom Api Endpoint](docs/configure-custom-api.md)\n- [Configure Microsft Entra ID App (OPTIONAL)](/docs/configure-entra-app.md)\n\n## Please Contribute\n\nI welcome all contributions here! Before you do, please read the [Contributors Guide](docs/CONTRIBUTING.md)\n\n## Third Party Libraries\n\nPresence Light would not be possible without the amazing work from the contributors to the following third party libraries!\n\n- Lights\n  - [Q42.HueApi](https://github.com/Q42/Q42.HueApi)\n  - [OpenWiz](https://github.com/UselessMnemonic/OpenWiz)\n  - [YeelightAPI](https://github.dev/roddone/YeelightAPI)\n  - [LifxCloud](https://github.com/isaacrlevin/LifxCloudClient)\n- UI Components\n  - [MudBlazor](https://www.mudblazor.com/)\n  - [Blazorise](https://github.com/Megabit/Blazorise)\n  - [BlazorPro.Spinkit](https://github.com/EdCharbeneau/BlazorPro.Spinkit)\n- Backend\n  - [MediatR](https://github.com/jbogard/MediatR)\n  - [Polly](https://github.com/App-vNext/Polly)\n  - [Serilog](https://github.com/serilog/serilog)\n  - [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)\n  - [IdentityModel.OidcClient](https://github.com/IdentityModel/IdentityModel.OidcClient)\n  - [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning)\n"
  },
  {
    "path": "chocolatey/PresenceLight.nuspec",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<package xmlns=\"http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd\">\n  <metadata>\n    <id>PresenceLight</id>\n    <version>$version$</version>\n    <title>PresenceLight</title>\n    <authors>Isaac Levin</authors>\n    <owners>Isaac Levin</owners>\n    <packageSourceUrl>https://github.com/isaacrlevin/PresenceLight</packageSourceUrl>\n    <docsUrl>https://github.com/isaacrlevin/presencelight/blob/main/README.md</docsUrl>\n    <bugTrackerUrl>https://github.com/isaacrlevin/PresenceLight/issues</bugTrackerUrl>\n    <licenseUrl>https://github.com/isaacrlevin/PresenceLight/blob/main/LICENSE</licenseUrl>\n    <projectUrl>https://github.com/isaacrlevin/PresenceLight/</projectUrl>\n    <projectSourceUrl>https://github.com/isaacrlevin/PresenceLight/</projectSourceUrl>\n    <iconUrl>https://rawcdn.githack.com/isaacrlevin/PresenceLight/2c388db6a155cf7bbc8b12578222e0609d147ee0/Icon.png</iconUrl>\n    <requireLicenseAcceptance>false</requireLicenseAcceptance>\n    <description>\nPresenceLight is a solution to broadcast your various statuses to a Philips Hue or LIFX light bulb.\nSome statuses you can broadcast are: your availability in Microsoft Teams, your current Windows 10 theme, and a theme or color of your choosing.\n\n## Package Parameters\n\n- InstallDir: Change installation directory of PresenceLight. Default is \"$env:APPDATA\\PresenceLight\"\n    </description>\n    <summary>Broadcasts colors to various Smart Lights</summary>\n    <releaseNotes>https://github.com/isaacrlevin/PresenceLight/releases</releaseNotes>\n    <tags>presence lifx hue philips teams</tags>\n  </metadata>\n  <files>\n    <file src=\"tools\\**\" target=\"tools\" />\n  </files>\n</package>\n"
  },
  {
    "path": "chocolatey/tools/ChocolateyBeforeModify.ps1",
    "content": "# Make sure to kill any presencelight processes before attempting an\n# uninstall or upgrade of PresenceLight\nGet-Process presencelight* -ErrorAction SilentlyContinue | Stop-Process"
  },
  {
    "path": "chocolatey/tools/ChocolateyInstall.ps1",
    "content": "$ErrorActionPreference  = 'Stop';\n\n# Make sure to kill any presencelight processes before attempting an\n# installation. This covers the case that PresenceLight is currently\n# installed outside of Chocolatey\nGet-Process presencelight* -ErrorAction SilentlyContinue | Stop-Process\n\n$WindowsVersion=[Environment]::OSVersion.Version\nif ($WindowsVersion.Major -ne \"10\") {\n  throw \"This package requires Windows 10.\"\n}\n\n$IsCorrectBuild=[Environment]::OSVersion.Version.Build\nif ($IsCorrectBuild -lt \"17134\") {\n  throw \"This package requires at least Windows 10 version build 17134.x.\"\n}\n\n$packageName    = \"presencelight\"\n$toolsDir       = \"$(Split-Path -parent $MyInvocation.MyCommand.Definition)\"\n$InstallDir     = Join-Path $env:APPDATA 'PresenceLight'\n\n$pp = Get-PackageParameters\n\nif($pp['InstallDir']){\n  $InstallDir = $pp['InstallDir']\n}\n\n$packageArgs = @{\n  packageName    = $packageName\n  unzipLocation  = $InstallDir\n  urlARM         = \"{ARMLink}\"\n  url86bit       = \"{x86Link}\"\n  url64bit       = \"{x64Link}\"\n  checksumARM    = \"{ReplaceCheckSumARM}\"\n  checksum       = \"{ReplaceCheckSumx86}\"\n  checksum64     = \"{ReplaceCheckSumx64}\"\n  checksumType   = 'SHA256'\n}\n\nInstall-ChocolateyZipPackage @packageArgs\n\n$exePath = Join-Path $InstallDir 'PresenceLight.exe'\n\n\nWrite-Output \"Adding shortcut to Start Menu\"\nInstall-ChocolateyShortcut -ShortcutFilePath \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\PresenceLight.lnk\" -TargetPath $exePath -WorkingDirectory $InstallDir\n\nWrite-Output \"Adding shortcut to Startup\"\nInstall-ChocolateyShortcut -ShortcutFilePath \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\PresenceLight.lnk\" -TargetPath $exePath -WorkingDirectory $InstallDir\n"
  },
  {
    "path": "chocolatey/tools/ChocolateyUninstall.ps1",
    "content": "# This logic can be removed after a couple of package version releases, since\n# this will be handled in the ChocolateyBeforeModify.ps1 going forward\n$light = Get-process presencelight*\n\nif($light){\n\t$light | Stop-Process -Force\n}\n\nRemove-Item $Env:AppData\\PresenceLight -Recurse -Force\nRemove-Item \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\PresenceLight.lnk\"\nRemove-Item \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\PresenceLight.lnk\""
  },
  {
    "path": "chocolatey/tools/LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2023 Isaac Levin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "chocolatey/tools/Verification.txt",
    "content": "VERIFICATION\nVerification is intended to assist the Chocolatey moderators and community\nin verifying that this package's contents are trustworthy.\n\nThe package has been generated by our CI system and binaries/scripts signed with Authenticode.\n\n\n1. Download Zipped Application with below Url\n\nPackage Zip Urls\n - xARM Link: {ARMLink}\n - x86 Link: {x86Link}\n - x64 Link: {x64Link}\n\n2. You can use one of the following methods to obtain the checksum\n  - Use powershell function 'Get-Filehash'\n  - Use chocolatey utility 'checksum.exe'\n\n  checksum type: sha256\n  checksum: {HASHx86}\n  checksum64: {HASHx64}\n  checksumARM:   {HASHARM}"
  },
  {
    "path": "docker-compose-example.yml",
    "content": "version: '3.7'\n\nservices:\n  presencelight:\n    image: isaaclevin/presencelight:latest\n    container_name: presencelight\n    restart: unless-stopped\n    ports:\n      - 5000:80\n      - 5001:443\n    volumes:\n      - /mnt/c/Users/isaac/.aspnet/https:/https:ro\n    environment:\n      ASPNETCORE_HTTPS_PORT: \"5001\"\n      ASPNETCORE_URLS: \"https://+;http://+\"\n      ASPNETCORE_Kestrel__Certificates__Default__Password: \"presencelight\"\n      ASPNETCORE_Kestrel__Certificates__Default__Path: \"/https/presencelight.pfx\""
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "content": "# Contributing to PresenceLight\n\nThank you for your interest in contributing to the PresenceLight project! We welcome contributions from the community to help improve and enhance the project. This guide will provide you with the necessary information to get started.\n\n## Table of Contents\n- [Contributing to PresenceLight](#contributing-to-presencelight)\n  - [Table of Contents](#table-of-contents)\n  - [Getting Started](#getting-started)\n  - [Setting Up Local Environment](#setting-up-local-environment)\n    - [Obtain Microsoft Entra Client ID.](#obtain-microsoft-entra-client-id)\n    - [Debugging Windows App](#debugging-windows-app)\n    - [Debugging Web App](#debugging-web-app)\n  - [Adding New Functionality](#adding-new-functionality)\n  - [Contributing Guidelines](#contributing-guidelines)\n  - [Submitting a Pull Request](#submitting-a-pull-request)\n  - [Code of Conduct](#code-of-conduct)\n  - [License](#license)\n\n## Getting Started\n\nTo contribute to PresenceLight, you will need to have the following prerequisites:\n\n- Basic knowledge of Git and GitHub.\n- Knowledge of the .NET framework and C# programming language (this project uses the latest version, [.NET 10](https://dot.net)).\n- IDE of choice ([Visual Studio 2022](https://visualstudio.microsoft.com/downloads/), [Visual Studio Code](https://code.visualstudio.com/Download), [JetBrains Rider](https://www.jetbrains.com/rider/download))\n- [Docker Desktop](https://www.docker.com/products/docker-desktop/) if you want to test the Web project running as a container.\n\n## Setting Up Local Environment\n\n### Obtain Microsoft Entra Client ID.\nPresenceLight WILL not work if you try to clone and run, because there is a dependency on Microsoft Entra. Because of this, if you want to contribute to the project at this time, please reach out to the maintainer, [Isaac Levin](mailto:isaac@isaaclevin.com) to obtain the Client ID and Client Secret. Once obtained, the Client ID will need to be placed in 2 locations. Firstly, create create a copy of appsettings.json in both the Desktop and Web Projects, calling this new file `appsettings.Development.json`. Place the Client Id, in the proper locations in each file\n- [Desktop Version](https://github.com/isaacrlevin/presencelight/blob/main/src/DesktopClient/PresenceLight/appsettings.json#L13)\n- [Web Version](https://github.com/isaacrlevin/presencelight/blob/main/src/PresenceLight.Web/appsettings.json#L6)\n\nIf you have access to your own Microsoft Entra tenant, you can also create your own Entra App and use the Client ID as well. More information on that is [here](configure-entra-app.md).\n\n### Debugging Windows App\n\nAfter this, the app should build and run without issue. You can run the Desktop version as either a standalone .NET WPF app or as a packaged app that is deployed locally to the Windows store.\n- To debug standalone version, set [PresenceLight](https://github.com/isaacrlevin/presencelight/blob/main/src/DesktopClient/PresenceLight/PresenceLight.csproj) as startup project\n- To debug Microsoft Store version, set to [PresenceLight.Package](https://github.com/isaacrlevin/presencelight/blob/main/src/DesktopClient/PresenceLight.Package/PresenceLight.Package.wapproj)\n  - NOTE: You may need to enable additional things in Visual Studio to make this work. More info [here](https://learn.microsoft.com/en-us/visualstudio/debugger/debug-installed-app-package)\n\n### Debugging Web App\n\nAfter adding the [Client ID](https://github.com/isaacrlevin/presencelight/blob/main/src/PresenceLight.Web/appsettings.json#L6) and [Client Secret](https://github.com/isaacrlevin/presencelight/blob/de14b62d0e6b433735eef653cee48d550747b60d/src/PresenceLight.Web/appsettings.json#L10), you should be able to debug the web version by setting the [Web Project](https://github.com/isaacrlevin/presencelight/blob/main/src/PresenceLight.Web/PresenceLight.Web.csproj) as startup.\n\n## Adding New Functionality\n\nIf you are adding new functionality to PresenceLight (adding support for a new light for instance), there are a handful of steps you will need to take to enable the functionality in all versions. To understand what you need to do, it would be helpful to understand what all projects in the solution do.\n\n- ### [PresenceLight.Core](https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Core)\n    This project holds all the shared logic for PresenceLight, including interfacing with Microsoft Entra, Microsoft Graph, all lights, as well as all the models that exist for the solution. More than likely, you will be working inside the [Lights](https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Core/Lights) folder in this project to add a new folder to include the code to support your feature. The project uses [MediatR](https://github.com/jbogard/MediatR) to send messages in-process across the application. Be sure to follow the existing pattern when adding Requests and Handlers\n\n- ### [PresenceLight.Razor]((https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Razor))\n  This project holds all of the UI for the application, and leverages ASP.NET Core Blazor components to achieve this. If you are adding new functionality, you will either update an existing component or add a new one. If you are adding a new component, you will add a new `.razor` file in the [Pages](https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Razor/Components/Pages) folder and add an entry in the `NavMenu.razor` for your new component. Please follow the existing patterns that you see in the other `.razor` files.\n\n- ### [PresenceLight](https://github.com/isaacrlevin/presencelight/tree/main/src/DesktopClient/PresenceLight)\n    This is the WPF project that runs the desktop version of the application. The application contains a single Window that runs all of the functionality (calling the Graph API, calling handlers to update lights). Once you are ready to test your functionality for the Desktop version, add code to light up the functionality in [`MainWindow.xaml.cs`](https://github.com/isaacrlevin/presencelight/blob/main/src/DesktopClient/PresenceLight/MainWindow.xaml.cs).\n\n- ### [PresenceLight.Package](https://github.com/isaacrlevin/presencelight/tree/main/src/DesktopClient/PresenceLight.Package)\n  This project wires up the WPF project to run in the Microsoft store. You should not need to update or add anything to this project.\n\n- ### [PresenceLight.Web](https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Web)\n    This is the project that runs the web version of the application. The project leverages a ASP.NET Core Worker Service to run the functionality that \"polls\" (calling Graph API, calling handlers for lights). Once you are ready to test your functionality for the Web version, add code to light up the functionality in [`Worker.cs`](https://github.com/isaacrlevin/presencelight/blob/main/src/PresenceLight.Web/Worker.cs).\n\n## Contributing Guidelines\n\nBefore you start contributing, please take a moment to review the following guidelines:\n\n1. Fork the repository and create a new branch for your contribution.\n2. Make sure your code follows the project's coding style and conventions.\n3. Write clear and concise commit messages.\n4. Test your changes thoroughly before submitting a pull request. It is important if you are adding new functionality to test both the Desktop AND Web versions.\n5. Document any new features or changes in the project's documentation.\n\n## Submitting a Pull Request\n\nOnce you have made your changes and are ready to submit a pull request, follow these steps:\n\n1. Push your changes to your forked repository.\n2. Go to the original repository and create a new pull request.\n3. Provide a clear and descriptive title for your pull request.\n4. Include a detailed description of the changes you have made.\n5. Wait for the project maintainers to review your pull request and provide feedback.\n\n## Code of Conduct\n\nPlease note that by contributing to the PresenceLight project, you are expected to adhere to the project's Code of Conduct. The CoC is simple, be respectful and considerate towards others in all interactions.\n\n## License\n\nPresenceLight is licensed under the [MIT License](https://github.com/isaacrlevin/presencelight/blob/main/LICENSE). By contributing to this project, you agree to license your contributions under the same license.\n\n---\n\nWe appreciate your contributions to the PresenceLight project! Thank you for helping us make it better.\n"
  },
  {
    "path": "docs/configure-custom-api.md",
    "content": "# Custom API\n\nThe Custom API page lets you use any generic service which has a web API which accepts GET or POST requests.\n\nFor example, IFTTT webhooks can be used to run an action on any IFTTT-integrated service.\n\nIn this way IFTTT can act as a bridge to other light services (such as Magic Home / MagicHue) or any other service which you may want to control with your Teams presence, e.g. 'When I'm in do not disturb pause Roomba'.\n\nTo connect PresenceLight to a custom API:\n\nConfigure the web service (e.g. created the applets in IFTTT)\nEnter the corresponding API method and URI against each presence state.\n\n   ![Configured](../static/CustomAPI.png)   \n  \nThe Custom API REST API calls also support providing a json formatted body to the endpoints (Uri) of Custom API.\n\nYou can use the following variables in your JSON body:\n\n- {{availability}}\n- {{activity}}\n\nIf you use above variables in the JSON body they will be replaced with the availability and/or activity values of your Microsoft Teams status.\n\n## Home Assistant integration\n\nTo use PresenceLight with Home Assistant you can use the Custom API functionality as follows:\n\nIn Home Assistant you can use [Webhooks triggers](https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger) to trigger an Automation Action, like turning on a light bulb.\n\nExample Automation for turning on a light bulb based on the Teams status send using the Custom API functionality of PresenceLight.\n\n```yaml\nalias: Teams presence - IKEA Light Bulb Living Room\ndescription: >-\n  Show the Microsoft Teams status via a color of the Light Bulb in the Living\n  Room\ntrigger:\n  - platform: webhook\n    allowed_methods:\n      - POST\n    local_only: true\n    webhook_id: \"<enter secret webhook id here>\"\ncondition: []\naction:\n  - choose:\n      - conditions:\n          - condition: template\n            value_template: \"{{ trigger.json.presence_status == 'Busy' }}\"\n        sequence:\n          - service: light.turn_on\n            metadata: {}\n            data:\n              color_name: red\n            target:\n              entity_id: light.ikea_bulb\n      - conditions:\n          - condition: template\n            value_template: \"{{ trigger.json.presence_status == 'Available' }}\"\n        sequence:\n          - service: light.turn_on\n            metadata: {}\n            data:\n              color_name: green\n            target:\n              entity_id: light.ikea_bulb\n      - conditions:\n          - condition: template\n            value_template: \"{{ trigger.json.presence_status == 'Away' }}\"\n        sequence:\n          - service: light.turn_on\n            metadata: {}\n            data:\n              color_name: yellow\n            target:\n              entity_id: light.ikea_bulb\n      - conditions: null\n        sequence:\n          - service: light.turn_off\n            metadata: {}\n            target:\n              entity_id: light.ikea_bulb\n            data: {}\nmode: single\n```\n\nIn PresenceLight Custom API setting you need to enter the following information:\n\n| Method | Uri            | Body |\n|--------|----------------|------|\n| POST   | http://homeassistant.local:8123/api/webhook/webhook_id |    {   \"presence_status\":\"Away\" }  |\n"
  },
  {
    "path": "docs/configure-entra-app.md",
    "content": "\n## Configure an Entra ID Application\n\n1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/) using either a work or school account or a personal Microsoft account.\n1. If your account gives you access to more than one tenant, select your account in the top right corner, and set your portal session to the desired Azure AD tenant\n   (using **Switch Directory**).\n1. In the left-hand navigation pane, select the **Entra ID** service, and then select **App registrations**.\n\n#### Register the client app (WpfApp)\n\n1. Navigate to the Microsoft identity platform for developers [App registrations](https://go.microsoft.com/fwlink/?linkid=2083908) page.\n1. Select **New registration**.\n   - In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `Presence Light`.\n   - In the **Supported account types** section, select **Accounts in this organizational directory only (YOUR_TENANT_NAME only - Single tenant)**.\n   - In the **Redirect URI (optional)** section, select **Public client/native (mobile & desktop)** and enter http://localhost for the value.\n    - Select **Register** to create the application.\n1. On the app **Overview** page, find the **Application (client) ID** value and record it for later.\n1. On the app **Overview** page, find the **Directory (tenant) ID** value and record it for later.<br>![Ids](../static/id.png)\n1. In the list of pages select **API permissions**\n   - Select **Add a permission**\n   - Ensure that the **Microsoft APIs** tab is selected\n   - In the **Commonly used Microsoft APIs** section, click on **Microsoft Graph**\n   - Select **Delegated permissions**.\n   - Ensure that the right permissions are checked: **Presence.Read, User.Read**. Use the search box if necessary. See the screenshot below.\n   - Select **Add permissions**\n   - You can consent for your entire organization by selecting **Grant admin consent for YOUR_TENANT_NAME**\n     - In the **Grant admin consent confirmation** section select **Yes**\n\n   ![Api Permissions](..//static/api-perms.png)\n\n#### Configuring PresenceLight (Desktop)\n\n1. Start `PresenceLight`.\n1. Select **Settings**\n  1. Enter your **Directory (tenant) ID** or `common` if you elected to support Multitenant account types.\n  1. Enter your **Application (client) ID**\n  1. Select **SAVE SETTINGS**\n1. Select **Team Status**\n  1. Select **SIGN IN**\n  1. Complete authentication in your browser."
  },
  {
    "path": "docs/configure-hardware.md",
    "content": "## Hue HW Notes\n\n### Hue Hardware Requirements\n\n| Item  |\n| ------------ |\n| [Philips Hue Bridge](https://www2.meethue.com/en-us/p/hue-bridge/046677458478)\n| [Philips Hue Light Bulb](https://www2.meethue.com/en-us/p/hue-white-and-color-ambiance-1-pack-e26/046677548483) |\n\nYou will need the above Philips Hue items to broadcast your presence to, but you can still \"use\" PresenceLight without them. One of the requirements of the Bridge is that it needs to be hard-wired to an internet connection via ethernet, so it will need to be placed close to a router or network switch. There are steps to setup the bridge and bulb in the [Hardware and Connectivity Section](https://www2.meethue.com/en-us/support/hardware-and-connectivity) of the Philips Support Site, but you should be able to just plug the bridge, wait for the lights to light up, get the IP address for the bridge, enter it into the app, and register the device. The app will register your device, create an account to interact with the bulbs, and finally add any bulbs it finds.\n\nPhilips also provides a Remote implementation of their connectivity (requires connecting your account to Philips Cloud). PresenceLight is configured to let you choose between the two.\n\n## LIFX HW Notes\n\n### LIFX Hardware Requirements\n [Any LIFX Light (tested with LIFX Beam & LIFX Color)](https://www.lifx.com/pages/all-products)\n\nLIFX Bulbs can be connected to over [LAN Protocol](https://lan.developer.lifx.com/), or [Cloud Api](https://lifx.readme.io/docs). PresenceLight uses the Cloud, which requires getting a token from the [developer portal](https://cloud.lifx.com/settings). Putting that token in PresenceLight will enable all connected lights.\n\n## Philips Wiz\n\n| Item  |\n| ------------ |\n| [Wiz Smart Bulb](https://www.wizconnected.com/en-us/products/bulbs)\n|\nPresenceLight uses LAN discovery to get all Philips Wiz smart bulbs on the network.\n\n## Yeelight\n| Item  |\n| ------------ |\n| [Yeelight Smart Bulb](https://store.yeelight.com/collections/smart-bulb)\n|\nPresenceLight uses LAN discovery to get all Yeelight smart bulbs on the network."
  },
  {
    "path": "docs/desktop-README.md",
    "content": "![Logo](https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png)\n# PresenceLight - Desktop Version\n\n![.github/workflows/Deploy_Desktop.yml](https://github.com/isaacrlevin/presencelight/workflows/.github/workflows/Deploy_Desktop.yml/badge.svg)\n\n## Desktop App Setup\n\n**NOTE: These steps are for the WPF (Windows desktop client) application. If you want to get PresenceLight working on non-Windows, please take a look at the [Web Readme](web-README.md).**\n\n\n\n\n### Install App\n\nAfter you have followed installed the app, you will see a window like below\n\n   ![Configured](../static/configured.png)\n\nPresenceLight obtains your Microsoft Teams Availability using a multi-tenant Microsoft Entra ID Application, meaning you will need to \"grant\" access to your Presence the first time you use the app. Clicking sign-in will prompt you for a login with your Microsoft 365 credentials, and finally when authenticated, you will be shown your Graph profile image and your presence. If you are curious about what is required to do this on your own tenant, read [Configure an Entra ID Application](configure-entra-app.md)\n\n   ![Profile Image](../static/profile.png)\n\nThe application \"polls\" the Presence Api at a configured value, which you can set between 1 and 5 seconds on the Settings page. This means that the light and app will update based on your Teams presence with a slight delay.\n\n### Broadcasting to Lights\n\nThere are 2 ways to currently update your lights using PresenceLight\n\n - Updating with Teams Presence (status)\n - Setting a fixed color using color picker\n\nYou can only do one of these at a time, so if you for instance are syncing with Teams, choosing another option will sign you out of Teams. This will happen with the other options as well.\n\n## Customize Icons\n\nOne of the features of PresenceLight is that you can minimize the app to the icon tray. When you open the app, you will see an icon similar to this.\n\n   ![white Image](../static/light-icon.png)\n\nThis icon will represent your presence color. There are two \"kinds\" of icons: Transparent, and White. Here is the transparent icon\n\n   ![Settings 1](../static/trans-icon.png)\n\nYou can change the icon type in the settings pane.\n\n   ![Settings 2](../static/settings1.png)\n\nAfter you change and save, the icon will update in the icon tray.\n\n## Wire Up Philips Light\n\nTo connect PresenceLight to Philips Hue, you can do it 1 of 2 ways\n\n - Obtain the IP Address of your Philips Hue Bridge (if you have it)\n - Ask PresenceLight to find it for you (may no work in certain network configurations)\n\n ![Hue Settings](../static/hue-settings.png)\n\nOnce you have the IP of the bridge, you will need to register a developer account and get an Api Key. This is easily done by clicking the \"Register Bridge\" button. Clicking the button will popup a window asking you to press the sync button on the bridge, this is needed to register PresenceLight to the bridge.\n\n ![Sync Button](../static/sync-button.png)\n\nWhen PresenceLight is configured, you will see a dropdown of Hue Bulbs connected to the bridge for you to set your presence to.\n\n ![Registered Bridge](../static/registered-bridge.png)\n\n## Wire up LIFX\n\nTo connect PresenceLight to LIFX colored bulbs, you need to obtain a LIFX Developer Token. When you first arrive at the LIFX tab, you will see a message like this if you try to get Lights or Groups\n\n ![LIFX Unconfigured](../static/lifx-unconfigured.png)\n\nAfter entering an obtained token, you will be able to get a list of either individual lights or groups of lights, selecting one of the options and saving gives you a message like this\n\n ![LIFX Configured](../static/lifx-configured.png)\n\n## [Wire-up Custom API](configure-custom-api.md)\n\n\n## In Conclusion\n\nAt this point PresenceLight should be setup. Feel free to file an issue if you have any problems."
  },
  {
    "path": "docs/faq.md",
    "content": "### What is the best version to install?\nIt really depends on your workflow, for normal users, I would use the Microsoft Store as it least barrier to entry.\n\n### How do I use my lights after installing PresenceLight?\nPresenceLight \"polls\" Graph and Windows Theme data until you tell it not to. The easiest way to do this is either shutdown the app, or do a one-time sync to a custom color, which should stop any polling.\n\n### Where is X light?\nI would love PresenceLight to support EVERY smart light on the market, but I do not have the hardware to do that, I simply wrote a tool with the HW I have. If you want to add your own Smart Light brand, a PR is the fastest way. If you do, please follow the [Contributors Guide](CONTRIBUTING.md)\n\n### This only runs on Windows, lame....\nI am currently working on a cross-platform version using ASP.NET Core Blazor and Workers. I have been testing it on WSL2 as well as a RaspberryPi I have at home and it seems to be working well. I will release more info about that [here](web-README.md)\n\n"
  },
  {
    "path": "docs/web-README.md",
    "content": "![Logo](Icon.png)\n# PresenceLight - Web Version\n![.github/workflows/Deploy_Web.yml](https://github.com/isaacrlevin/presencelight/workflows/.github/workflows/Deploy_Web.yml/badge.svg)\n\nThe cross platform version of PresenceLight runs as a .NET 9 single file executable application that runs a Server-Side Blazor Web Application and a ASP.NET Core Worker Service. The Blazor App is used as the mechanism to log the user in and configure settings of the app, while the Worker Service is responsible for interaction with Graph Api as well as the Smart Lights. This allows users to not need to have a UI version of the app open at all time, since the worker runs as a process.\n## App Setup\n\n### Prerequisites\n\nFor PresenceLight to run out of the box, you need to setup a local SSL Cert for the app to run under. Here are two ways to do this\n\n- dotnet dev-certs\n  - dotnet dev-certs https -ep C:\\Users\\youruserid\\.aspnet\\https\\presencelight.pfx -p presencelight\n  - dotnet dev-certs https --trust\n- openssl (Linux)\n  - [Go here make your life easier](https://www.digicert.com/easy-csr/openssl.htm)\n  - openssl x509 -signkey my_web_domain.key -in my_web_domain.csr -req -days 365 -out my_web_domain.crt\n  - openssl pkcs12 -inkey my_web_domain.key -in my_web_domain.crt -export -out %PATHTOYOURCERT%/presencelight.pfx\n\n### Install\n\nThere is no installer for PresenceLight, so all that needs to be done is to download the zip folder from the [install site](http://presencelightapp.azurewebsites.net/), unzip, and run the .exe. At this point, a terminal window will open showing\n\n ![Terminal](../static/blazor-terminal.png)\n\nHere you will the Url for the Kestrel hosted Web Application, which will be `https://localhost:5001`. Going to that Url will take you through the login process for Azure Active Directory (for the Graph call). After login, you will see a similar look and feel to the client app.\n\n ![Index](../static/blazor-index.png)\n\n From here you can use PresenceLight in a similar way to the client app. You can enable and operate lights, push custom lights and configure polling. When done, you can close the browser and PresenceLight will continue to run in the background.\n\n To make the process even cleaner, you can configure a startup task to run the exe at startup, and PresenceLight will be available at the url listed the first time you ran it.\n\n## Running PresenceLight as a container\n\nPresenceLight can be configured to run in a Docker container, and I have images on my [DockerHub](https://hub.docker.com/repository/docker/isaaclevin/presencelight) for the primary Linux distros.\n\n- x64 Linux (latest tag)\n- ARM64 (debian-arm64 tag)\n- ARM32 (debian-arm32 tag) **This is the 4GB Raspberry Pi one**\n\n### How are you handling SSL?\n\nIn order for PresenceLight to work, you need to have a redirect url to AAD\nthat is https. In order to make it easy for folks, I provided a self-signed cert that will allow PresenceLight to do Https redirection out of the box. Isaac is this secure? Weeeeeeelllll not the best, but since PresenceLight runs locally you should be fine. If you want to expose PresenceLight over the internet, it more than likely won't work as I have to register EACH redirect uri with Azure AD.\n\nFor my particular use-case I do not need SSL. WHAT?!?! Actually it is pretty cool. My personal setup is that PresenceLight runs in a docker container on a Raspberry Pi. I have Traefik, which is a well-known\nreverse proxy that allows me to forward applications through my domain, so I can access the application from anywhere by going to\n\npresencelight.mydomain.com\n\nThe best part about this is that [Traefik](https://traefik.io/) can be configured to pull LetsEncrypt Certificates and integration with CloudFlare SSL. There is a [great blog post on this](https://www.smarthomebeginner.com/traefik-2-docker-tutorial/), that I highly reccomend if you are interested.\n\n### SSL for Docker Containers\n\nTo get PresenceLight to work in a Docker container, you will need to obtain (or generate like above) a certificate and mount it as a volume to your container.\n\n**[Doc on subject](https://docs.microsoft.com/dotnet/core/additional-tools/self-signed-certificates-guide)**\n\nOnce you have a valid .pfx file, you will need to wire up the app to use that cert, the way you do that depends on how you host your app. If you app is just running locally on the machine,\nyou can just set environment variables for your app.\n\n- ASPNETCORE_Kestrel__Certificates__Default__Path\n- ASPNETCORE_Kestrel__Certificates__Default__Password\n\nOr if you are running in docker, you will need to mount a volume that has your cert in it.\n\nHere are some examples for this\n\n**docker run example**\n\n```bash\ndocker run --rm -it -p 5000:80 -p 5001:443 -e ASPNETCORE_URLS=\"https://+;http://+\" -e ASPNETCORE_HTTPS_PORT=5001 -e ASPNETCORE_Kestrel__Certificates__Default__Password=\"presencelight\" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/presencelight.pfx -v $env:USERPROFILE\\.aspnet\\https:/https/ isaaclevin/presencelight\n```\n\n\n**docker-compose example**\n\n```bash\nports:\n  - 5000:80\n  - 5001:443\nvolumes:\n  C:\\Users\\isaac\\.aspnet\\https:/https/ #Windows Way\n  /mnt/c/Users/isaac/.aspnet/https:/https #Linux Way\nenvironment:\n  ASPNETCORE_HTTPS_PORT: \"5001\"\n  ASPNETCORE_URLS: \"https://+;http://+\"\n  ASPNETCORE_Kestrel__Certificates__Default__Password: \"presencelight\"\n  ASPNETCORE_Kestrel__Certificates__Default__Path: \"/https/presencelight.pfx\"\n```\n\n### Mounting settings path to host\n\nIf you want to configure PresenceLight to use your own settings (maybe your own AAD, your own smart light registered app), you can do that by editing the appsettings.json\n\nTo do this in docker, just run the container once, and than stop and rerun by mounting the appsettings via a local volume.**\n\nLog data and Configuration file will need to be written to a directory that has read/write enabled.   This is accomplished using\nvolumes.\n```dotnetcli\nvolumes:\n    /somedirectory:/app/config\n```\n\n`/app/config/appsettings.json` contains settings for AAD and `/app/config/PresenceLightSettings.json` contains settings for Lights, in case you wanted to configure lights outside of the UI. If you need to customize your configuration. Add/edit one or more of the nec`essary configuration files in this attached directory. This will get you host access to the appsettings.json and PresenceLightSettings.json\n\nWhen running under a container, logs will save to  `/app/config/logs` as well."
  },
  {
    "path": "src/.editorconfig",
    "content": "# EditorConfig is awesome:http://EditorConfig.org\n# From https://raw.githubusercontent.com/dotnet/roslyn/master/.editorconfig\n\n# top-most EditorConfig file\nroot = true\n\n# Don't use tabs for indentation.\n[*]\nindent_style = space\ntrim_trailing_whitespace = true\n# (Please don't specify an indent_size here; that has too many unintended consequences.)\n\n# Code files\n[*.{cs,csx,vb,vbx}]\nindent_size = 4\ninsert_final_newline = true\ncharset = utf-8-bom\n\n# Xml project files\n[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]\nindent_size = 2\n\n# Xml config files\n[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]\nindent_size = 2\n\n# Yml/Yaml files\n[*.{yaml,yml}]\nindent_size = 2\n\n# Powershell files\n[*.ps1]\nindent_size = 2\n\n# JSON files\n[*.json]\nindent_size = 2\n\n# Shell scripts\n[*.sh]\nend_of_line = lf\n\n[*.{cmd,bat}]\nend_of_line = crlf\n\n# Dotnet code style settings:\n[*.{cs,vb}]\n# Sort using and Import directives with System.* appearing first\ndotnet_sort_system_directives_first = true\n# Put a blank line between System.* and Microsoft.*\ndotnet_separate_import_directive_groups = true\n\n# Avoid \"this.\" and \"Me.\" if not necessary\ndotnet_style_qualification_for_field = false:suggestion\ndotnet_style_qualification_for_property = false:suggestion\ndotnet_style_qualification_for_method = false:suggestion\ndotnet_style_qualification_for_event = false:suggestion\n\n# Use language keywords instead of framework type names for type references\ndotnet_style_predefined_type_for_locals_parameters_members = true:suggestion\ndotnet_style_predefined_type_for_member_access = true:suggestion\n\n# Prefer read-only on fields\ndotnet_style_readonly_field = false:warning\n\n# Suggest more modern language features when available\ndotnet_style_object_initializer = true:suggestion\ndotnet_style_collection_initializer = true:suggestion\ndotnet_style_coalesce_expression = true:suggestion\ndotnet_style_null_propagation = true:suggestion\ndotnet_style_explicit_tuple_names = true:suggestion\ndotnet_style_prefer_inferred_tuple_names = true:suggestion\ndotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion\ndotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion\ndotnet_style_prefer_conditional_expression_over_return = false\ndotnet_style_prefer_conditional_expression_over_assignment = false\ndotnet_style_prefer_auto_properties = true:suggestion\n\n# Parentheses\ndotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent\ndotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent\ndotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent\ndotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent\n\n# Accessibility modifiers\ndotnet_style_require_accessibility_modifiers = always:suggestion\n\n\n# Naming Rules\n\n# Interfaces start with an I and are PascalCased\ndotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.symbols                        = interface_symbols\ndotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.style                          = pascal_case_and_prefix_with_I_style\ndotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.severity                       = warning\n\n# External members are PascalCased\ndotnet_naming_rule.externally_visible_members_must_be_pascal_cased.symbols                            = externally_visible_symbols\ndotnet_naming_rule.externally_visible_members_must_be_pascal_cased.style                              = pascal_case_style\ndotnet_naming_rule.externally_visible_members_must_be_pascal_cased.severity                           = warning\n\n# Parameters are camelCased\ndotnet_naming_rule.parameters_must_be_camel_cased.symbols                                             = parameter_symbols\ndotnet_naming_rule.parameters_must_be_camel_cased.style                                               = camel_case_style\ndotnet_naming_rule.parameters_must_be_camel_cased.severity                                            = warning\n\n# Constants are PascalCased\ndotnet_naming_rule.constants_must_be_pascal_cased.symbols                                             = constant_symbols\ndotnet_naming_rule.constants_must_be_pascal_cased.style                                               = pascal_case_style\ndotnet_naming_rule.constants_must_be_pascal_cased.severity                                            = warning\n\n# Uncomment this group and comment out the next group if you prefer s_ prefixes for static fields\n\n# Private static fields are prefixed with s_ and are camelCased like s_myStatic\n#dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.symbols   = private_static_field_symbols\n#dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.style     = camel_case_and_prefix_with_s_underscore_style\n#dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.severity  = warning\n\n# Static readonly fields are PascalCased\ndotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols                               = private_static_readonly_field_symbols\ndotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style                                 = pascal_case_style\ndotnet_naming_rule.static_readonly_fields_should_be_pascal_case.severity                              = warning\n\n# Comment this group and uncomment out the next group if you don't want _ prefixed fields.\n\n# Private instance fields are camelCased with an _ like _myField\ndotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.symbols   = private_field_symbols\ndotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.style     = camel_case_and_prefix_with_underscore_style\ndotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.severity  = warning\n\n# Private instance fields are camelCased\n#dotnet_naming_rule.private_instance_fields_must_be_camel_cased.symbols                                = private_field_symbols\n#dotnet_naming_rule.private_instance_fields_must_be_camel_cased.style                                  = camel_case_style\n#dotnet_naming_rule.private_instance_fields_must_be_camel_cased.severity                               = warning\n\n# Symbols\ndotnet_naming_symbols.externally_visible_symbols.applicable_kinds                                     = class,struct,interface,enum,property,method,field,event,delegate\ndotnet_naming_symbols.externally_visible_symbols.applicable_accessibilities                           = public,internal,friend,protected,protected_internal,protected_friend,private_protected\n\ndotnet_naming_symbols.interface_symbols.applicable_kinds                                              = interface\ndotnet_naming_symbols.interface_symbols.applicable_accessibilities                                    = *\n\ndotnet_naming_symbols.parameter_symbols.applicable_kinds                                              = parameter\ndotnet_naming_symbols.parameter_symbols.applicable_accessibilities                                    = *\n\ndotnet_naming_symbols.constant_symbols.applicable_kinds                                               = field\ndotnet_naming_symbols.constant_symbols.required_modifiers                                             = const\ndotnet_naming_symbols.constant_symbols.applicable_accessibilities                                     = *\n\ndotnet_naming_symbols.private_static_field_symbols.applicable_kinds                                   = field\ndotnet_naming_symbols.private_static_field_symbols.required_modifiers                                 = static,shared\ndotnet_naming_symbols.private_static_field_symbols.applicable_accessibilities                         = private\n\ndotnet_naming_symbols.private_static_readonly_field_symbols.applicable_kinds                          = field\ndotnet_naming_symbols.private_static_readonly_field_symbols.required_modifiers                        = static,shared,readonly\ndotnet_naming_symbols.private_static_readonly_field_symbols.applicable_accessibilities                = private\n\ndotnet_naming_symbols.private_field_symbols.applicable_kinds                                          = field\ndotnet_naming_symbols.private_field_symbols.applicable_accessibilities                                = private\n\n# Styles\ndotnet_naming_style.camel_case_style.capitalization                                                   = camel_case\n\ndotnet_naming_style.pascal_case_style.capitalization                                                  = pascal_case\n\ndotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.required_prefix                     = s_\ndotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.capitalization                      = camel_case\n\ndotnet_naming_style.camel_case_and_prefix_with_underscore_style.required_prefix                       = _\ndotnet_naming_style.camel_case_and_prefix_with_underscore_style.capitalization                        = camel_case\n\ndotnet_naming_style.pascal_case_and_prefix_with_I_style.required_prefix                               = I\ndotnet_naming_style.pascal_case_and_prefix_with_I_style.capitalization                                = pascal_case\n\n\n# CSharp code style settings:\n[*.cs]\n# Modifier order\ncsharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion\n\n# Code block\ncsharp_prefer_braces = false:none\n\n# Indentation preferences\ncsharp_indent_block_contents = true\ncsharp_indent_braces = false\ncsharp_indent_case_contents = true\ncsharp_indent_switch_labels = true\ncsharp_indent_labels = flush_left\n\n# Prefer \"var\" everywhere\ncsharp_style_var_for_built_in_types = true:suggestion\ncsharp_style_var_when_type_is_apparent = true:suggestion\ncsharp_style_var_elsewhere = true:suggestion\n\n# Code style defaults\ncsharp_preserve_single_line_blocks = true\ncsharp_preserve_single_line_statements = true\n\n# Prefer method-like constructs to have a block body\ncsharp_style_expression_bodied_methods = false:none\ncsharp_style_expression_bodied_constructors = false:none\ncsharp_style_expression_bodied_operators = false:none\n\n# Prefer property-like constructs to have an expression-body\ncsharp_style_expression_bodied_properties = true:none\ncsharp_style_expression_bodied_indexers = true:none\ncsharp_style_expression_bodied_accessors = true:none\n\n# Expression \ncsharp_prefer_simple_default_expression = true:suggestion\ncsharp_style_deconstructed_variable_declaration = true:suggestion\ncsharp_style_pattern_local_over_anonymous_function = true:suggestion\n\n# Pattern matching\ncsharp_style_pattern_matching_over_is_with_cast_check = true:suggestion\ncsharp_style_pattern_matching_over_as_with_null_check = true:suggestion\ncsharp_style_inlined_variable_declaration = true:suggestion\n\n# Null checking preferences\ncsharp_style_throw_expression = true:suggestion\ncsharp_style_conditional_delegate_call = true:suggestion\n\n# Newline settings\ncsharp_new_line_before_open_brace = all\ncsharp_new_line_before_else = true\ncsharp_new_line_before_catch = true\ncsharp_new_line_before_finally = true\ncsharp_new_line_before_members_in_object_initializers = true\ncsharp_new_line_before_members_in_anonymous_types = true\ncsharp_new_line_between_query_expression_clauses = true\n\n# Space preferences\ncsharp_space_after_cast = false\ncsharp_space_after_colon_in_inheritance_clause = true\ncsharp_space_after_comma = true\ncsharp_space_after_dot = false\ncsharp_space_after_keywords_in_control_flow_statements = true\ncsharp_space_after_semicolon_in_for_statement = true\ncsharp_space_around_binary_operators = before_and_after\ncsharp_space_around_declaration_statements = do_not_ignore\ncsharp_space_before_colon_in_inheritance_clause = true\ncsharp_space_before_comma = false\ncsharp_space_before_dot = false\ncsharp_space_before_open_square_brackets = false\ncsharp_space_before_semicolon_in_for_statement = false\ncsharp_space_between_empty_square_brackets = false\ncsharp_space_between_method_call_empty_parameter_list_parentheses = false\ncsharp_space_between_method_call_name_and_opening_parenthesis = false\ncsharp_space_between_method_call_parameter_list_parentheses = false\ncsharp_space_between_method_declaration_empty_parameter_list_parentheses = false\ncsharp_space_between_method_declaration_name_and_open_parenthesis = false\ncsharp_space_between_method_declaration_parameter_list_parentheses = false\ncsharp_space_between_parentheses = false\ncsharp_space_between_square_brackets = false\n\n# CA1303: Do not pass literals as localized parameters\ndotnet_diagnostic.CA1303.severity = none\n\n# CA1051: Do not declare visible instance fields\ndotnet_diagnostic.CA1051.severity = none\n\n# CA1031: Do not catch general exception types\ndotnet_diagnostic.CA1031.severity = none\n\n# CA1812: Avoid uninstantiated internal classes\ndotnet_diagnostic.CA1812.severity = silent\n\n# CA1816: Dispose methods should call SuppressFinalize\ndotnet_diagnostic.CA1816.severity = silent\n\n# CA1054: Uri parameters should not be strings\ndotnet_diagnostic.CA1054.severity = none\ndotnet_diagnostic.CA1056.severity = none\n\n# Default severity for analyzer diagnostics with category 'Style'\ndotnet_analyzer_diagnostic.category-Style.severity = silent\n"
  },
  {
    "path": "src/DesktopClient/Directory.Build.props",
    "content": "<Project>\n\n  <PropertyGroup>\n    <Authors>Isaac Levin</Authors>\n    <Copyright>© 2023 Isaac Levin</Copyright>\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n    <DefaultLanguage>en-US</DefaultLanguage>\n    <NoWarn>1701;1702;1705;1591;NU1701</NoWarn>\n\n    <IsLegacyProject>$(MSBuildProjectName.Equals('PresenceLight'))</IsLegacyProject>\n    <IsPackageProject>$(MSBuildProjectName.Contains('.Package'))</IsPackageProject>\n    <DebugType>embedded</DebugType>\n\n    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>\n    <PublishRepositoryUrl>true</PublishRepositoryUrl>\n    <EmbedUntrackedSources>true</EmbedUntrackedSources>\n    <UseWpf>true</UseWpf>\n    <UseWindowsForms>true</UseWindowsForms>\n    <MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);NETSDK1107</MSBuildWarningsAsMessages>\n\n    <LangVersion>preview</LangVersion>\n    <NullableContextOptions>enable</NullableContextOptions>\n    <Nullable>enable</Nullable>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <NuGetDependencyVersion>5.8.0</NuGetDependencyVersion>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.SourceLink.GitHub\" Version=\"1.0.0\" PrivateAssets=\"All\"/>\n  </ItemGroup>\n\n  <PropertyGroup>\n    <ReleaseChannel Condition=\"'$(ChannelName)' == '' \">Debug</ReleaseChannel>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <DefineConstants Condition=\"'$(ChannelName)' == 'Nightly' \">$(DefineConstants);NIGHTLY</DefineConstants>\n    <DefineConstants Condition=\"'$(ChannelName)' == 'Release' \">$(DefineConstants);RELEASE</DefineConstants>\n    <DefineConstants Condition=\"'$(ChannelName)' == 'Standalone' \">$(DefineConstants);STANDALONE</DefineConstants>\n  </PropertyGroup>\n\n</Project>\n"
  },
  {
    "path": "src/DesktopClient/Directory.Build.targets",
    "content": "<Project>\n\n  <Target Name=\"AddCommitHashToAssemblyAttributes\" BeforeTargets=\"GetAssemblyAttributes\">\n    <ItemGroup>\n      <AssemblyAttribute Include=\"System.Reflection.AssemblyMetadataAttribute\" Condition=\" '$(SourceRevisionId)' != '' \">\n        <_Parameter1>CommitHash</_Parameter1>\n        <_Parameter2>$(SourceRevisionId)</_Parameter2>\n      </AssemblyAttribute>\n\n      <AssemblyAttribute Include=\"System.Reflection.AssemblyMetadataAttribute\" Condition=\" '$(PublicRelease)' == 'true' \">\n        <_Parameter1>CloudBuildNumber</_Parameter1>\n        <_Parameter2>$(BuildVersionSimple)</_Parameter2>\n      </AssemblyAttribute>\n      <AssemblyAttribute Include=\"System.Reflection.AssemblyMetadataAttribute\" Condition=\" '$(PublicRelease)' == 'false' \">\n        <_Parameter1>CloudBuildNumber</_Parameter1>\n        <_Parameter2>$(BuildVersionSimple)$(SemVerBuildSuffix)</_Parameter2>\n      </AssemblyAttribute>\n    </ItemGroup>\n\n  </Target>\n</Project>"
  },
  {
    "path": "src/DesktopClient/PresenceLight/App.xaml",
    "content": "﻿<Application x:Class=\"PresenceLight.App\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:local=\"clr-namespace:PresenceLight\"\n             Startup=\"OnStartup\">\n    <Application.Resources>\n         \n    </Application.Resources>\n</Application>\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/App.xaml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.IO;\nusing System.Windows;\n\nusing Blazorise;\nusing Blazorise.Bootstrap;\nusing Blazorise.Icons.FontAwesome;\n\nusing Microsoft.ApplicationInsights.Extensibility;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\n\nusing MudBlazor.Services;\n\nusing PresenceLight.Core;\nusing PresenceLight.Razor;\nusing PresenceLight.Razor.Services;\nusing PresenceLight.Services;\nusing PresenceLight.Telemetry;\n\nusing Serilog;\n\nusing Windows.Storage;\n\nnamespace PresenceLight\n{\n    /// <summary>\n    /// Interaction logic for App.xaml\n    /// </summary>\n    public partial class App : System.Windows.Application\n    {\n        public IServiceProvider? ServiceProvider { get; private set; }\n\n        public IConfiguration? Configuration { get; private set; }\n\n        public App()\n        {\n\n\n        }\n\n        private void OnStartup(object sender, StartupEventArgs e)\n        {\n            if (SingleInstanceAppMutex.TakeExclusivity())\n            {\n                Exit += (_, __) => SingleInstanceAppMutex.ReleaseExclusivity();\n\n                try\n                {\n                    ContinueStartup();\n                }\n                catch (Exception ex) when (IsCriticalFontLoadFailure(ex))\n                {\n                    Trace.WriteLine($\"## Warning Notify ##: {ex}\");\n                    Log.Error(ex, \"Stopped program because of exception\");\n                }\n            }\n            else\n            {\n                Log.CloseAndFlush();\n                Shutdown();\n            }\n        }\n\n        Dictionary<string, string> InMemorySettings = new();\n\n        private void ContinueStartup()\n        {\n            IServiceCollection services = new ServiceCollection();\n\n            // Configuration Section\n            var builder = new Microsoft.Extensions.Configuration.ConfigurationBuilder()\n                 .AddJsonFile(\"appsettings.json\", optional: false, reloadOnChange: true)\n                 .AddJsonFile($\"appsettings.Development.json\", optional: true, reloadOnChange: true);\n\n            string userAppSettings;\n\n            //Override the save file location for logs if this is a packaged app... \n            if (new DesktopBridge.Helpers().IsRunningAsUwp())\n            {\n                var _logFilePath = System.IO.Path.Combine(ApplicationData.Current.LocalFolder.Path, \"PresenceLight\\\\logs\\\\DesktopClient\\\\log-.json\");\n                InMemorySettings.Add(\"Serilog:WriteTo:1:Args:Path\", _logFilePath);\n                builder.AddInMemoryCollection(InMemorySettings);\n\n                userAppSettings = AppPackageSettingsService.BuildSettingsFileLocation();\n            }\n            else\n            {\n                userAppSettings = StandaloneSettingsService.BuildSettingsFileLocation();\n            }\n            \n            builder.AddJsonFile(userAppSettings, optional: true, reloadOnChange: true);\n\n            Configuration = builder.Build();\n\n            services.Configure<BaseConfig>(Configuration);\n            services.AddSingleton(Configuration);\n            services.AddOptions();\n            services.Configure<AADSettings>(Configuration.GetSection(\"AADSettings\"));\n\n            //Logging\n            var telemetryConfiguration = TelemetryConfiguration.CreateDefault();\n            telemetryConfiguration.InstrumentationKey = Configuration[\"ApplicationInsights:InstrumentationKey\"];\n\n\n\n            var loggerConfig =\n            new LoggerConfiguration()\n                          .ReadFrom.Configuration(Configuration)\n                          .WriteTo.PresenceEventsLogSink()\n                          .Enrich.FromLogContext();\n\n\n            Log.Logger = loggerConfig.CreateLogger();\n            Log.Debug(\"Starting PresenceLight\");\n\n            services.AddLogging(logging =>\n            {\n                logging.AddSerilog();\n            });\n\n#if DEBUG\n            services.AddBlazorWebViewDeveloperTools();\n#endif\n\n\n            services.Configure<TelemetryConfiguration>((o) =>\n            {\n                o.InstrumentationKey = Configuration[\"ApplicationInsights:InstrumentationKey\"];\n                o.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());\n            });\n            services.AddApplicationInsightsTelemetryWorkerService(options =>\n            {\n                options.EnablePerformanceCounterCollectionModule = false;\n                options.EnableDependencyTrackingTelemetryModule = false;\n            });\n\n\n\n            //Blazor\n\n            services.AddMudServices();\n\n            services.AddHttpClient();\n            services.AddHttpContextAccessor();\n            services.AddWpfBlazorWebView();\n\n            services.AddBlazorise(options =>\n                 {\n                     options.Immediate = true;\n                 })\n    .AddBootstrapProviders()\n    .AddFontAwesomeIcons();\n\n            services.AddMediatR(cfg =>\n            {\n                cfg.RegisterServicesFromAssembly(typeof(App).Assembly);\n                cfg.RegisterServicesFromAssembly(typeof(BaseConfig).Assembly);\n            });\n\n            //Singleton Services\n            services.AddSingleton<AppState>();\n            services.AddSingleton<AppInfo, AppInfo>();\n\n            services.AddSingleton<LoginService, LoginService>();\n            services.AddSingleton<AuthorizationProvider, AuthorizationProvider>();\n\n\n\n            services.AddPresenceServices();\n\n            services.AddSingleton<LIFXOAuthHelper, LIFXOAuthHelper>();\n            services.AddSingleton<MainWindow>();\n            services.AddTransient<DiagnosticsClient, DiagnosticsClient>();\n\n            if (new DesktopBridge.Helpers().IsRunningAsUwp())\n            {\n                services.AddSingleton<ISettingsService, AppPackageSettingsService>();\n            }\n            else\n            {\n                services.AddSingleton<ISettingsService, StandaloneSettingsService>();\n            }\n\n            services.AddSingleton<ITelemetryInitializer, AppVersionTelemetryInitializer>();\n\n            //Inject Services Into MainWindow\n            ServiceProvider = services.BuildServiceProvider();\n\n            var configuration = ServiceProvider.GetService<TelemetryConfiguration>();\n\n            if (configuration != null)\n            {\n                var b = configuration.DefaultTelemetrySink.TelemetryProcessorChainBuilder;\n                double fixedSamplingPercentage = 10;\n                b.UseSampling(fixedSamplingPercentage);\n                b.Build();\n            }\n            var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();\n            mainWindow.Show();\n        }\n\n        private static bool IsCriticalFontLoadFailure(Exception ex)\n        {\n            return ex.StackTrace.Contains(\"MS.Internal.Text.TextInterface.FontFamily.GetFirstMatchingFont\", StringComparison.OrdinalIgnoreCase) ||\n                   ex.StackTrace.Contains(\"MS.Internal.Text.Line.Format\", StringComparison.OrdinalIgnoreCase);\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/MainWindow.xaml",
    "content": "﻿<Window x:Class=\"PresenceLight.MainWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:l=\"clr-namespace:PresenceLight\"\n        xmlns:gif=\"http://wpfanimatedgif.codeplex.com\"\n        xmlns:xctk=\"http://schemas.xceed.com/wpf/xaml/toolkit\"\n        xmlns:local=\"clr-namespace:PresenceLight.Razor.Components;assembly=PresenceLight.Razor\"\n        xmlns:blazor=\"clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf\"\n        mc:Ignorable=\"d\" Title=\"PresenceLight\" Icon=\"Icons/Icon.ico\" Height=\"1200\" Width=\"1200\" MinWidth=\"600px\">\n    <Grid>\n        <blazor:BlazorWebView x:Name=\"blazorWebView1\" HostPage=\"wwwroot\\index.html\" Services=\"{StaticResource services}\" Loaded=\"MainWindow_Loaded\">\n            <blazor:BlazorWebView.RootComponents>\n                <blazor:RootComponent Selector=\"#app\" ComponentType=\"{x:Type local:PresenceLightClientApp}\" />\n            </blazor:BlazorWebView.RootComponents>\n        </blazor:BlazorWebView>\n        <l:NotifyIcon x:Name=\"notificationIcon\" MouseDoubleClick=\"OnNotifyIconDoubleClick\">\n            <l:NotifyIcon.ContextMenu>\n                <ContextMenu>\n                    <MenuItem Header=\"Open\" Click=\"OnOpenClick\" />\n                    <MenuItem Header=\"Turn Off Sync\" x:Name=\"turnOffButton\" Click=\"OnTurnOffSyncClick\" />\n                    <MenuItem Header=\"Turn On Sync\" x:Name=\"turnOnButton\" Click=\"OnTurnOnSyncClick\" />\n                    <MenuItem Header=\"Exit\" Click=\"OnExitClick\" />\n                </ContextMenu>\n            </l:NotifyIcon.ContextMenu>\n        </l:NotifyIcon>\n    </Grid>\n</Window>\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/MainWindow.xaml.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Input;\nusing System.Windows.Media;\nusing System.Windows.Media.Imaging;\n\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing Microsoft.Graph;\nusing Microsoft.Graph.Models;\nusing Microsoft.Identity.Client;\n\nusing PresenceLight.Core;\n\nusing PresenceLight.Telemetry;\n\nnamespace PresenceLight\n{\n    /// <summary>\n    /// Interaction logic for MainWindow.xaml\n    /// </summary>\n    public partial class MainWindow : Window\n    {\n        private readonly BaseConfig _options;\n\n\n        // private Presence presence { get; set; }\n        private DateTime settingsLastSaved = DateTime.MinValue;\n\n        private MediatR.IMediator _mediator;\n\n        private readonly LoginService _loginService;\n        private DiagnosticsClient _diagClient;\n        private ISettingsService _settingsService;\n        private WindowState lastWindowState;\n        private bool isInteractRunning;\n        private readonly ILogger<MainWindow> _logger;\n        private readonly AppState _appState = new AppState();\n\n        #region Init\n        public MainWindow(LoginService loginService,\n                          MediatR.IMediator mediator,\n                          IOptionsMonitor<BaseConfig> optionsAccessor,\n                          DiagnosticsClient diagClient,\n                          ILogger<MainWindow> logger,\n                          ISettingsService settingsService,\n                          AppState appState)\n        {\n            var currentApp = (App)System.Windows.Application.Current;\n            Resources.Add(\"services\", currentApp.ServiceProvider);\n            InitializeComponent();\n            _appState = appState;\n\n            _logger = logger;\n            System.Windows.Application.Current.SessionEnding += new SessionEndingCancelEventHandler(Current_SessionEnding);\n\n            _loginService = loginService;\n\n\n            _mediator = mediator;\n            _options = optionsAccessor != null ? optionsAccessor.CurrentValue : throw new NullReferenceException(\"Options Accessor is null\");\n            _diagClient = diagClient;\n            _settingsService = settingsService;\n\n            LoadSettings().ContinueWith(\n                async t =>\n                {\n                    if (t.IsFaulted)\n                    {\n                        var foo = \"\";\n                    }\n\n                    await Task.Run(async () =>\n                    {\n                        this.Dispatcher.Invoke(() =>\n                        {\n                            appState.SignedIn = false;\n                            LoadApp();\n\n                            var tbContext = notificationIcon.DataContext;\n                            DataContext = _appState.Config;\n                            notificationIcon.DataContext = tbContext;\n\n                            if (_appState.Config.StartMinimized)\n                            {\n                                this.Hide();\n                            }\n\n                        });\n\n                        while (true)\n                        {\n\n                            await Task.Run(async () =>\n                            {\n                                Thread.Sleep(100);\n                                if (_appState.SignInRequested)\n                                {\n                                    _appState.SignInRequested = false;\n\n                                    await this.Dispatcher.BeginInvoke(async () =>\n                                     {\n                                         await SignIn();\n                                     });\n                                }\n\n                                if (_appState.SignOutRequested)\n                                {\n                                    _appState.SignOutRequested = false;\n                                    await this.Dispatcher.BeginInvoke(async () =>\n                                     {\n                                         await SignOut();\n                                     });\n                                }\n\n                                if (_appState.RebuildRequested)\n                                {\n                                    _appState.RebuildRequested = false;\n                                    await this.Dispatcher.BeginInvoke(async () =>\n                                     {\n                                         await RebuildClient();\n                                     });\n                                }\n                            });\n                        }\n                    });\n                }, TaskScheduler.Current);\n        }\n\n        private async Task LoadSettings()\n        {\n            try\n            {\n                _logger.LogInformation(\"Load Settings Initialized\");\n                if (!(await _settingsService.IsFilePresent()))\n                {\n                    await _settingsService.SaveSettings(_options);\n                }\n\n                _appState.SetConfig(await _settingsService.LoadSettings() ?? throw new NullReferenceException(\"Settings Load Service Returned null\"));\n\n                bool useWorkingHours = await _mediator.Send(new Core.WorkingHoursServices.UseWorkingHoursCommand());\n                bool IsInWorkingHours = await _mediator.Send(new Core.WorkingHoursServices.IsInWorkingHoursCommand());\n                _logger.LogInformation(\"Load Settings Successfull\");\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error occurred Loading Settings\");\n                _diagClient.TrackException(e);\n            }\n        }\n        private async void MainWindow_Loaded(object sender, RoutedEventArgs e)\n        {\n            await blazorWebView1.WebView.EnsureCoreWebView2Async();\n            blazorWebView1.WebView.CoreWebView2.Settings.IsZoomControlEnabled = false;\n        }\n        private void LoadApp()\n        {\n            try\n            {\n                notificationIcon.Text = $\"PresenceLight Status - {PresenceConstants.Inactive}\";\n                notificationIcon.Icon = new BitmapImage(new Uri(IconConstants.GetIcon(string.Empty, string.Empty)));\n\n                _appState.Config.LightSettings.WorkingHoursStartTimeAsDate = string.IsNullOrEmpty(_appState.Config.LightSettings.WorkingHoursStartTime) ? null : DateTime.Parse(_appState.Config.LightSettings.WorkingHoursStartTime, null);\n                _appState.Config.LightSettings.WorkingHoursEndTimeAsDate = string.IsNullOrEmpty(_appState.Config.LightSettings.WorkingHoursEndTime) ? null : DateTime.Parse(_appState.Config.LightSettings.WorkingHoursEndTime, null);\n\n                CallGraph();\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, $\"Error occurred - {e.Message}\");\n            }\n        }\n\n        #endregion\n\n        #region Profile Panel\n\n        private async Task SignIn()\n        {\n            await CallGraph();\n        }\n\n        private async Task CallGraph()\n        {\n            _appState.SetLightMode(\"Graph\");\n            _logger.LogInformation(\"Light Mode Set: Graph\");\n            if (!await _mediator.Send(new Core.GraphServices.GetIsInitializedCommand()))\n            {\n                await _mediator.Send(new Core.GraphServices.InitializeCommand()\n                {\n                });\n\n                if (_loginService.IsInitialized)\n                {\n                    _appState.SignedIn = true;\n                }\n            }\n\n            try\n            {\n                await _settingsService.SaveSettings(_appState.Config);\n\n                if (!isInteractRunning)\n                {\n                    await InteractWithLights();\n                }\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error occurred calling Graph\");\n            }\n        }\n\n        public async Task SetColor(string color, string activity = \"\")\n        {\n            try\n            {\n                if (_appState.Config.LightSettings.Hue.IsEnabled)\n                {\n                    if (Helpers.AreStringsNotEmpty(new string[] {\n                                                    _appState.Config.LightSettings.Hue.HueApiKey,\n                                                    _appState.Config.LightSettings.Hue.SelectedItemId}))\n                    {\n                        if (_appState.Config.LightSettings.Hue.UseRemoteApi)\n                        {\n                            if (!string.IsNullOrEmpty(_appState.Config.LightSettings.Hue.RemoteBridgeId))\n                            {\n                                await _mediator.Send(new Core.RemoteHueServices.SetColorCommand\n                                {\n                                    Availability = color,\n                                    Activity = activity,\n                                    LightId = _appState.Config.LightSettings.Hue.SelectedItemId,\n                                    BridgeId = _appState.Config.LightSettings.Hue.RemoteBridgeId\n                                });\n                            }\n                        }\n                        if (!string.IsNullOrEmpty(_appState.Config.LightSettings.Hue.HueIpAddress))\n                        {\n                            await _mediator.Send(new Core.HueServices.SetColorCommand()\n                            {\n                                Activity = activity,\n                                Availability = color,\n                                LightID = _appState.Config.LightSettings.Hue.SelectedItemId\n                            });\n                        }\n                    }\n                }\n\n                if (_appState.Config.LightSettings.LIFX.IsEnabled && !string.IsNullOrEmpty(_appState.Config.LightSettings.LIFX.LIFXApiKey))\n                {\n                    await _mediator.Send(new PresenceLight.Core.LifxServices.SetColorCommand { Activity = activity, Availability = color, LightId = _appState.Config.LightSettings.LIFX.SelectedItemId });\n\n                }\n\n                if (_appState.Config.LightSettings.Wiz.IsEnabled)\n                {\n                    await _mediator.Send(new PresenceLight.Core.WizServices.SetColorCommand { Activity = activity, Availability = color, LightID = _appState.Config.LightSettings.Wiz.SelectedItemId });\n\n                }\n\n                if (_appState.Config.LightSettings.Yeelight.IsEnabled && !string.IsNullOrEmpty(_appState.Config.LightSettings.Yeelight.SelectedItemId))\n                {\n                    await _mediator.Send(new PresenceLight.Core.YeelightServices.SetColorCommand { Activity = activity, Availability = color, LightId = _appState.Config.LightSettings.Yeelight.SelectedItemId });\n\n                }\n\n                if (_appState.Config.LightSettings.CustomApi.IsEnabled)\n                {\n                    string response = await _mediator.Send(new Core.CustomApiServices.SetColorCommand() { Activity = activity, Availability = color });\n                }\n\n                if (_appState.Config.LightSettings.LocalSerialHost.IsEnabled)\n                {\n                    string response = await _mediator.Send(new Core.LocalSerialHostServices.SetColorCommand() { Activity = activity, Availability = color });\n                }\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error occurred Setting Color\");\n            }\n        }\n\n        private async Task SignOut()\n        {\n            _logger.LogInformation(\"Signing out of Graph PresenceLight Sync\");\n\n            _appState.SetLightMode(\"Graph\");\n            try\n            {\n                await _loginService.SignOut();\n\n                _appState.SignedIn = false;\n                _appState.SetUserInfo(null, null, null);\n                notificationIcon.Text = $\"PresenceLight Status - {PresenceConstants.Inactive}\";\n                notificationIcon.Icon = new BitmapImage(new Uri(IconConstants.GetIcon(string.Empty, string.Empty)));\n\n                await SetColor(\"Off\");\n            }\n            catch (MsalException)\n            {\n            }\n\n            await _settingsService.SaveSettings(_appState.Config);\n        }\n\n        private async Task RebuildClient()\n        {\n            _appState.RebuildRequested = false;\n            await SignOut();\n            _loginService.RebuildClient();\n            //this was called by signout before, but reusing it here to trigger NotifyStateChanged\n            _appState.SetUserInfo(null, null, null);\n        }\n\n#endregion\n\n#region UI Helpers\n        private BitmapImage? LoadImage(byte[] imageData)\n        {\n            try\n            {\n                if (imageData == null || imageData.Length == 0) return null;\n                var image = new BitmapImage();\n                using (var mem = new MemoryStream(imageData))\n                {\n                    mem.Position = 0;\n                    image.BeginInit();\n                    image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;\n                    image.CacheOption = BitmapCacheOption.OnLoad;\n                    image.UriSource = null;\n                    image.StreamSource = mem;\n                    image.EndInit();\n                }\n                image.Freeze();\n                return image;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error occurred in LoadImager\");\n                throw;\n            }\n        }\n\n        public void MapUI(Presence presence)\n        {\n            try\n            {\n                SolidColorBrush mySolidColorBrush = new SolidColorBrush();\n                if (presence != null)\n                {\n                    notificationIcon.Text = $\"PresenceLight Status - {Helpers.HumanifyText(presence.Availability)}\";\n                    notificationIcon.Icon = new BitmapImage(new Uri(IconConstants.GetIcon(_appState.Config.IconType, presence.Availability)));\n                }\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Mapping UI\");\n                throw;\n            }\n        }\n        #endregion\n\n        #region Graph Calls\n        public async Task<Presence> GetPresence()\n        {\n            try\n            {\n                return await _mediator.Send(new Core.GraphServices.GetPresenceCommand());\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error occurred Getting Presence\");\n                throw;\n            }\n        }\n\n        public async Task<byte[]?> GetPhoto()\n        {\n            try\n            {\n                var photo = await _mediator.Send(new Core.GraphServices.GetPhotoCommand());\n\n                if (photo == null)\n                {\n                    return null;\n                }\n                else\n                {\n                    return StreamToByteArray(photo);\n                }\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error occurred Getting Photo\");\n                return null;\n            }\n        }\n\n        public static byte[] StreamToByteArray(Stream input)\n        {\n            byte[] buffer = new byte[16 * 1024];\n            using (MemoryStream ms = new MemoryStream())\n            {\n                int read;\n                while ((read = input.Read(buffer, 0, buffer.Length)) > 0)\n                {\n                    ms.Write(buffer, 0, read);\n                }\n                return ms.ToArray();\n            }\n        }\n\n        #endregion\n\n        #region Tray Methods\n\n        protected override async void OnClosing(System.ComponentModel.CancelEventArgs e)\n        {\n            e.Cancel = true;\n            await _settingsService.SaveSettings(_appState.Config);\n            this.Hide();\n        }\n\n        private void OnNotifyIconDoubleClick(object sender, MouseButtonEventArgs e)\n        {\n            if (e.ChangedButton == MouseButton.Left)\n            {\n                this.Show();\n                this.WindowState = this.lastWindowState;\n            }\n        }\n\n        private void OnOpenClick(object sender, RoutedEventArgs e)\n        {\n            this.Show();\n            this.WindowState = this.lastWindowState;\n        }\n\n        private void OnTurnOnSyncClick(object sender, RoutedEventArgs e)\n        {\n            _appState.SetLightMode(\"Graph\");\n\n            this.WindowState = this.lastWindowState;\n            _logger.LogInformation(\"Turning On PresenceLight Sync\");\n        }\n\n        private async void OnTurnOffSyncClick(object sender, RoutedEventArgs e)\n        {\n            try\n            {\n                _appState.SetLightMode(\"Custom\");\n                await SetColor(\"Off\", \"Off\");\n\n                notificationIcon.Text = PresenceConstants.Inactive;\n                notificationIcon.Icon = new BitmapImage(new Uri(IconConstants.GetIcon(string.Empty, string.Empty)));\n\n                this.WindowState = this.lastWindowState;\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error occurred turning Off Sync\");\n            }\n            _logger.LogInformation(\"Turning Off PresenceLight Sync\");\n        }\n\n        private async void OnExitClick(object sender, RoutedEventArgs e)\n        {\n            try\n            {\n                await SetColor(\"Off\", \"Off\");\n\n                await _settingsService.SaveSettings(_appState.Config);\n                System.Windows.Application.Current.Shutdown();\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error occurred Exiting\");\n            }\n            _logger.LogInformation(\"PresenceLight Exiting\");\n        }\n\n        private async void Current_SessionEnding(object sender, SessionEndingCancelEventArgs e)\n        {\n            try\n            {\n                await SetColor(\"Off\", \"Off\");\n\n                await _settingsService.SaveSettings(_appState.Config);\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error occurred Ending Session\");\n            }\n\n            _logger.LogInformation(\"PresenceLight Session Ending\");\n        }\n#endregion\n\n        private async Task InteractWithLights()\n        {\n            bool previousWorkingHours = false;\n            string previousLightMode = string.Empty;\n            while (true)\n            {\n                isInteractRunning = true;\n                try\n                {\n                    if (_appState.SignedIn)\n                    {\n                        if (_appState.User == null || string.IsNullOrEmpty(_appState.User.DisplayName))\n                        {\n                            try\n                            {\n                                var (profile, presence) = await _mediator.Send(new Core.GraphServices.GetProfileAndPresenceCommand());\n                                \n\n                                var photo = await GetPhoto();\n\n                                _appState.SetLightMode(\"Graph\");\n\n                                if (photo == null)\n                                {\n                                    MapUI(presence);\n                                    _appState.SetUserInfo(profile, presence);\n                                }\n                                else\n                                {\n                                    MapUI(presence);\n                                    _appState.SetUserInfo(profile, presence, $\"data:image/gif;base64,{Convert.ToBase64String(photo)}\");\n                                }\n                            }\n                            catch (ServiceException ex)\n                            {\n                                if (ex.ResponseStatusCode == (int) System.Net.HttpStatusCode.Unauthorized ||\n                                    ex.ResponseStatusCode == (int) System.Net.HttpStatusCode.Forbidden)\n                                {\n                                    _logger.LogWarning(\"Error getting profile and presence info. Something is likely corrupt. Requesting sign out.\");\n                                    _appState.SignOutRequested = true;\n                                }\n                            }\n                        }\n                        await Task.Delay(Convert.ToInt32(_appState.Config.LightSettings.PollingInterval * 1000));\n\n                        bool touchLight = false;\n                        string newColor = \"\";\n\n                        if (_appState.Config.LightSettings.SyncLights)\n                        {\n                            if (!await _mediator.Send(new Core.WorkingHoursServices.UseWorkingHoursCommand()))\n                            {\n                                if (_appState.LightMode == \"Graph\")\n                                {\n                                    touchLight = true;\n                                }\n                            }\n                            else\n                            {\n                                var isInWorkingHours = await _mediator.Send(new Core.WorkingHoursServices.IsInWorkingHoursCommand());\n                                if (isInWorkingHours)\n                                {\n                                    previousWorkingHours = isInWorkingHours;\n                                    if (_appState.LightMode == \"Graph\")\n                                    {\n                                        touchLight = true;\n                                    }\n                                }\n                                else\n                                {\n                                    // check to see if working hours have passed\n                                    if (previousWorkingHours)\n                                    {\n                                        previousWorkingHours = false;\n                                        previousLightMode = _appState.LightMode;\n                                        switch (_appState.Config.LightSettings.HoursPassedStatus)\n                                        {\n\n                                            case \"White\":\n                                                newColor = \"Offline\";\n                                                _appState.SetLightMode(\"Manual\");\n                                                break;\n                                            case \"Off\":\n                                                newColor = \"Off\";\n                                                _appState.SetLightMode(\"Manual\");\n                                                break;\n                                            case \"Keep\":\n                                            default:\n                                                break;\n                                        }\n\n                                        touchLight = true;\n                                    }\n                                }\n                            }\n                        }\n\n                        if (touchLight && _appState.SignedIn)\n                        {\n                            switch (_appState.LightMode)\n                            {\n                                case \"Manual\":\n                                    // No need to check presence... if it's after hours, we just want to action upon it... \n                                    await SetColor(newColor, _appState.Presence.Activity);\n                                    //Reset the light mode so that we don't potentially mess something up.\n                                    _appState.SetLightMode(previousLightMode);\n                                    break;\n                                case \"Graph\":\n                                    _logger.LogInformation(\"PresenceLight Running in Teams Mode\");\n\n                                    _appState.SetPresence(await System.Threading.Tasks.Task.Run(() => GetPresence()));\n\n                                    if (newColor == string.Empty)\n                                    {\n                                        await SetColor(_appState.Presence.Availability, _appState.Presence.Activity);\n                                    }\n                                    else\n                                    {\n                                        await SetColor(newColor, _appState.Presence.Activity);\n                                    }\n                                    if (DateTime.Now.AddMinutes(-5) > settingsLastSaved)\n                                    {\n                                        await _settingsService.SaveSettings(_appState.Config);\n                                        settingsLastSaved = DateTime.Now;\n                                    }\n\n                                    MapUI(_appState.Presence);\n                                    break;\n                                default:\n                                    break;\n                            }\n                        }\n                    }\n                    else\n                    {\n                        isInteractRunning = false;\n                        break;\n                    }\n                }\n                catch (Exception e)\n                {\n                    _logger.LogError(e, \"Error occurred interacting with lights\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/PresenceLight.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Razor\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0-windows10.0.19041</TargetFramework>\n    <OutputType>WinExe</OutputType>\n    <AssemblyName>PresenceLight</AssemblyName>\n    <Title>PresenceLight</Title>\n    <Description>PresenceLight is a solution to broadcast your Microsoft Teams presence to a Philips Hue or LIFX light bulb. There are other solutions that do something similar, but they require a tethered solution (plugging a light into a computer via USB). What PresenceLight does is leverage the Presence Api, which is available in Microsoft Graph, allowing to retrieve your presence without having to be tethered. This could potentially allow someone to update the light bulb from a remote machine they do not use.</Description>\n    <RootNamespace>PresenceLight</RootNamespace>\n    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n    <TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>\n    <ApplicationManifest>Properties\\app.manifest</ApplicationManifest>\n    <EnableWindowsTargeting>true</EnableWindowsTargeting>\n    <ApplicationIcon>Icons\\Icon.ico</ApplicationIcon>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"DesktopBridge.Helpers\" Version=\"1.2.2\" />\n    <PackageReference Include=\"Microsoft.ApplicationInsights.WorkerService\" Version=\"2.23.0\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Components.WebView.Wpf\" Version=\"10.0.10\" />\n    <PackageReference Update=\"Microsoft.CodeAnalysis.FxCopAnalyzers\" Version=\"3.3.2\" />\n    <PackageReference Update=\"Microsoft.SourceLink.GitHub\" Version=\"8.0.0\" />\n    <PackageReference Update=\"Nerdbank.GitVersioning\" Version=\"3.6.133\" />\n    <PackageReference Include=\"Serilog.Sinks.ApplicationInsights\" Version=\"4.1.0\" />\n    <ProjectReference Include=\"..\\..\\PresenceLight.Core\\PresenceLight.Core.csproj\" />\n    <ProjectReference Include=\"..\\..\\PresenceLight.Razor\\PresenceLight.Razor.csproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Resource Include=\"Icons\\Available.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\BusyIdle.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\AvailableIdle.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\Away.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\BeRightBack.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\Busy.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\DoNotDisturb.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\Icon.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\Icon.png\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\Offline.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\Inactive.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\loading.gif\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\OutOfOffice.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\t_Available.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\t_BusyIdle.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\t_AvailableIdle.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\t_Away.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\t_BeRightBack.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\t_Busy.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\t_DoNotDisturb.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n    <Resource Include=\"Icons\\t_OutOfOffice.ico\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Resource>\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Update=\"appsettings.Development.json\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </None>\n    <None Update=\"appsettings.json\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </None>\n  </ItemGroup>\n\n    <ItemGroup>\n    <Content Update=\"wwwroot\\**\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Content>\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/PresenceLight.exe.gui",
    "content": ""
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/AssemblyInfo.cs",
    "content": "using System.Windows;\n\n[assembly:ThemeInfo(\n    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located\n                                     //(used if a resource is not found in the page,\n                                     // or application resource dictionaries)\n    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located\n                                              //(used if a resource is not found in the page,\n                                              // app, or any theme specific resource dictionaries)\n)]\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinARM64.pubxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nhttps://go.microsoft.com/fwlink/?LinkID=208121.\n-->\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <PublishProtocol>FileSystem</PublishProtocol>\n    <Platform>ARM64</Platform>\n    <TargetFramework>net10.0-windows10.0.19041</TargetFramework>\n    <RuntimeIdentifier>win-arm64</RuntimeIdentifier>\n    <PublishDir>bin\\$(Configuration)\\$(TargetFramework)\\$(RuntimeIdentifier)\\publish\\</PublishDir>\n    <SelfContained>true</SelfContained>\n    <PublishSingleFile>False</PublishSingleFile>\n    <PublishReadyToRun>False</PublishReadyToRun>\n    <PublishTrimmed>False</PublishTrimmed>\n  </PropertyGroup>\n</Project>\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinX64.pubxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nhttps://go.microsoft.com/fwlink/?LinkID=208121.\n-->\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <PublishProtocol>FileSystem</PublishProtocol>\n    <Platform>x64</Platform>\n    <TargetFramework>net10.0-windows10.0.19041</TargetFramework>\n    <RuntimeIdentifier>win-x64</RuntimeIdentifier>\n    <PublishDir>bin\\$(Configuration)\\$(TargetFramework)\\$(RuntimeIdentifier)\\publish\\</PublishDir>\n    <SelfContained>true</SelfContained>\n    <PublishSingleFile>False</PublishSingleFile>\n    <PublishReadyToRun>False</PublishReadyToRun>\n    <PublishTrimmed>False</PublishTrimmed>\n  </PropertyGroup>\n</Project>\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinX86.pubxml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nhttps://go.microsoft.com/fwlink/?LinkID=208121.\n-->\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <PublishProtocol>FileSystem</PublishProtocol>\n    <Platform>x86</Platform>\n    <TargetFramework>net10.0-windows10.0.19041</TargetFramework>\n    <RuntimeIdentifier>win-x86</RuntimeIdentifier>\n    <PublishDir>bin\\$(Configuration)\\$(TargetFramework)\\$(RuntimeIdentifier)\\publish\\</PublishDir>\n    <SelfContained>true</SelfContained>\n    <PublishSingleFile>False</PublishSingleFile>\n    <PublishReadyToRun>False</PublishReadyToRun>\n    <PublishTrimmed>False</PublishTrimmed>\n  </PropertyGroup>\n</Project>\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.42000\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace PresenceLight.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"16.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"PresenceLight.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).\n        /// </summary>\n        internal static System.Drawing.Icon Available {\n            get {\n                object obj = ResourceManager.GetObject(\"Available\", resourceCulture);\n                return ((System.Drawing.Icon)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).\n        /// </summary>\n        internal static System.Drawing.Icon Away {\n            get {\n                object obj = ResourceManager.GetObject(\"Away\", resourceCulture);\n                return ((System.Drawing.Icon)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).\n        /// </summary>\n        internal static System.Drawing.Icon BeRightBack {\n            get {\n                object obj = ResourceManager.GetObject(\"BeRightBack\", resourceCulture);\n                return ((System.Drawing.Icon)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).\n        /// </summary>\n        internal static System.Drawing.Icon Busy {\n            get {\n                object obj = ResourceManager.GetObject(\"Busy\", resourceCulture);\n                return ((System.Drawing.Icon)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).\n        /// </summary>\n        internal static System.Drawing.Icon DoNotDisturb {\n            get {\n                object obj = ResourceManager.GetObject(\"DoNotDisturb\", resourceCulture);\n                return ((System.Drawing.Icon)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).\n        /// </summary>\n        internal static System.Drawing.Icon Icon {\n            get {\n                object obj = ResourceManager.GetObject(\"Icon\", resourceCulture);\n                return ((System.Drawing.Icon)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).\n        /// </summary>\n        internal static System.Drawing.Icon Inactive {\n            get {\n                object obj = ResourceManager.GetObject(\"Inactive\", resourceCulture);\n                return ((System.Drawing.Icon)(obj));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Windows.Forms\" name=\"System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  <data name=\"Available\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Icons\\Available.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Away\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Icons\\Away.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"BeRightBack\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Icons\\BeRightBack.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Busy\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Icons\\Busy.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"DoNotDisturb\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Icons\\DoNotDisturb.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Icon\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Icons\\Icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Inactive\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Icons\\Inactive.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n</root>"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/Settings.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.42000\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace PresenceLight.Properties {\n    \n    \n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator\", \"16.7.0.0\")]\n    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {\n        \n        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));\n        \n        public static Settings Default {\n            get {\n                return defaultInstance;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/Settings.settings",
    "content": "﻿<?xml version='1.0' encoding='utf-8'?>\n<SettingsFile xmlns=\"uri:settings\" CurrentProfile=\"(Default)\">\n  <Profiles>\n    <Profile Name=\"(Default)\" />\n  </Profiles>\n  <Settings />\n</SettingsFile>"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<asmv1:assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\" xmlns:asmv1=\"urn:schemas-microsoft-com:asm.v1\" xmlns:asmv2=\"urn:schemas-microsoft-com:asm.v2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <assemblyIdentity version=\"1.0.0.0\" name=\"MyApplication.app\" />\n  <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v2\">\n    <security>\n      <requestedPrivileges xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n        <!-- UAC Manifest Options\n            If you want to change the Windows User Account Control level replace the \n            requestedExecutionLevel node with one of the following.\n        <requestedExecutionLevel  level=\"asInvoker\" uiAccess=\"false\" />\n        <requestedExecutionLevel  level=\"requireAdministrator\" uiAccess=\"false\" />\n        <requestedExecutionLevel  level=\"highestAvailable\" uiAccess=\"false\" />\n            Specifying requestedExecutionLevel node will disable file and registry virtualization.\n            If you want to utilize File and Registry Virtualization for backward \n            compatibility then delete the requestedExecutionLevel node.\n        -->\n        <requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" />\n      </requestedPrivileges>\n      <applicationRequestMinimum>\n        <defaultAssemblyRequest permissionSetReference=\"Custom\" />\n        <PermissionSet class=\"System.Security.PermissionSet\" version=\"1\" ID=\"Custom\" SameSite=\"site\" Unrestricted=\"true\" />\n      </applicationRequestMinimum>\n    </security>\n  </trustInfo>\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!-- A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.-->\n      <!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->\n      <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" />\n      <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" /> <!-- Windows 8 -->\n      <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" /> <!-- Windows 8.1 -->\n      <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" /> <!-- Windows 10 -->\n    </application>\n  </compatibility>\n  <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->\n  <dependency>\n    <dependentAssembly>\n      <assemblyIdentity\n          type=\"win32\"\n          name=\"Microsoft.Windows.Common-Controls\"\n          version=\"6.0.0.0\"\n          processorArchitecture=\"*\"\n          publicKeyToken=\"6595b64144ccf1df\"\n          language=\"*\"\n        />\n    </dependentAssembly>\n  </dependency>\n\n  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher\n       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need \n       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should \n       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->\n\n  <application xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n    <windowsSettings>\n      <!-- Per Monitor V1 [OS >= Windows 8.1] \n         Values: False, True, Per-monitor, True/PM -->\n      <dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">\n        true/PM\n      </dpiAware>\n      <!-- Per Monitor V1 [OS >= Windows 10 Anniversary Update (1607, 10.0.14393, Redstone 1)]\n         Values: Unaware, System, PerMonitor -->\n      <!-- Per Monitor V2 [OS >= Windows 10 Creators Update (1703, 10.0.15063, Redstone 2)]\n         Value: PerMonitorV2 -->\n      <dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">\n        PerMonitorV2, PerMonitor\n      </dpiAwareness>\n    </windowsSettings>\n  </application>\n</asmv1:assembly>"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/Constants.cs",
    "content": "﻿\nusing System;\nusing System.Reflection;\n\nnamespace PresenceLight\n{\n    public static class PresenceConstants\n    {\n        public const string Inactive = \"Not Logged In\";\n    }\n\n    public class PresenceColors\n    {\n        public const string Available = \"#009933\";\n        public const string AvailableIdle = \"#FFFF00\";\n        public const string Busy = \"#FF3300\";\n        public const string BusyIdle = \"#FFFF00\";\n        public const string BeRightBack = \"#FFFF00\";\n        public const string Away = \"#FFFF00\";\n        public const string DoNotDisturb = \"#B03CDE\";\n        public const string OutOfOffice = \"#800080\";\n        public const string Offline = \"#FFFFFF\";\n        public const string Inactive = \"#FFFFFF\";\n\n        public static string GetColor(string status)\n        {\n            var pc = new PresenceColors();\n            Type type =pc.GetType();\n            PropertyInfo[] props = type.GetProperties();\n\n            foreach (var prop in props)\n            {\n                if (prop.Name == status)\n                {\n                    return prop.GetValue(pc).ToString();\n                }\n            }\n            return PresenceColors.Inactive;\n        }\n    }\n\n    public static class IconConstants\n    {\n        private static string Base = \"pack://application:,,,/PresenceLight;component/icons/\";\n\n        public static string GetIcon(string iconType, string status)\n        {\n            if (string.IsNullOrEmpty(status))\n            {\n                status = \"Inactive\";\n            }\n            if (iconType == \"Transparent\")\n            {\n                return $\"{Base}t_{status}.ico\";\n            }\n            else\n            {\n                return $\"{Base}{status}.ico\";\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/MessageBoxHelper.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing System.Windows;\nusing System.Windows.Interop;\nnamespace PresenceLight\n{\n    internal static class MessageBoxHelper\n    {\n        internal static void PrepToCenterMessageBoxOnForm(Window form)\n        {\n            MessageBoxCenterHelper helper = new MessageBoxCenterHelper();\n            helper.Prep(form);\n        }\n\n        private class MessageBoxCenterHelper\n        {\n            private int messageHook;\n            private IntPtr parentFormHandle;\n\n            public void Prep(Window form)\n            {\n                NativeMethods.CenterMessageCallBackDelegate callBackDelegate = new NativeMethods.CenterMessageCallBackDelegate(CenterMessageCallBack);\n                GCHandle.Alloc(callBackDelegate);\n\n                parentFormHandle = new WindowInteropHelper(form).Handle;\n                messageHook = NativeMethods.SetWindowsHookEx(5, callBackDelegate, new IntPtr(NativeMethods.GetWindowLong(parentFormHandle, -6)), NativeMethods.GetCurrentThreadId()).ToInt32();\n            }\n\n            private int CenterMessageCallBack(int message, int wParam, int lParam)\n            {\n                NativeMethods.RECT formRect;\n                NativeMethods.RECT messageBoxRect;\n                int xPos;\n                int yPos;\n\n                if (message == 5)\n                {\n                    NativeMethods.GetWindowRect(parentFormHandle, out formRect);\n                    NativeMethods.GetWindowRect(new IntPtr(wParam), out messageBoxRect);\n\n                    xPos = (int)((formRect.Left + (formRect.Right - formRect.Left) / 2) - ((messageBoxRect.Right - messageBoxRect.Left) / 2));\n                    yPos = (int)((formRect.Top + (formRect.Bottom - formRect.Top) / 2) - ((messageBoxRect.Bottom - messageBoxRect.Top) / 2));\n\n                    NativeMethods.SetWindowPos(wParam, 0, xPos, yPos, 0, 0, 0x1 | 0x4 | 0x10);\n                    NativeMethods.UnhookWindowsHookEx(messageHook);\n                }\n\n                return 0;\n            }\n        }\n\n        private static class NativeMethods\n        {\n            internal struct RECT\n            {\n                public int Left;\n                public int Top;\n                public int Right;\n                public int Bottom;\n            }\n\n            internal delegate int CenterMessageCallBackDelegate(int message, int wParam, int lParam);\n\n            [DllImport(\"user32.dll\")]\n            [return: MarshalAs(UnmanagedType.Bool)]\n            internal static extern bool UnhookWindowsHookEx(int hhk);\n\n            [DllImport(\"user32.dll\", SetLastError = true)]\n            internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);\n\n            [DllImport(\"kernel32.dll\")]\n            internal static extern int GetCurrentThreadId();\n\n            [DllImport(\"user32.dll\", SetLastError = true)]\n            internal static extern IntPtr SetWindowsHookEx(int hook, CenterMessageCallBackDelegate callback, IntPtr hMod, int dwThreadId);\n\n            [DllImport(\"user32.dll\")]\n            [return: MarshalAs(UnmanagedType.Bool)]\n            internal static extern bool SetWindowPos(int hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);\n\n            [DllImport(\"user32.dll\")]\n            [return: MarshalAs(UnmanagedType.Bool)]\n            internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/NotifyIcon.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Windows;\nusing System.Windows.Input;\nusing System.Windows.Markup;\nusing System.Windows.Media;\nusing Drawing = System.Drawing;\nusing Forms = System.Windows.Forms;\n\nnamespace PresenceLight\n{\n\t[ContentProperty(\"Text\")]\n\t[DefaultEvent(\"MouseDoubleClick\")]\n\tpublic partial class NotifyIcon : FrameworkElement, IAddChild\n\t{\n\t\tpublic static readonly RoutedEvent MouseClickEvent = EventManager.RegisterRoutedEvent(\n\t\t\t\"MouseClick\",\n\t\t\tRoutingStrategy.Bubble,\n\t\t\ttypeof(MouseButtonEventHandler),\n\t\t\ttypeof(NotifyIcon));\n\n\t\tpublic static readonly RoutedEvent MouseDoubleClickEvent = EventManager.RegisterRoutedEvent(\n\t\t\t\"MouseDoubleClick\",\n\t\t\tRoutingStrategy.Bubble,\n\t\t\ttypeof(MouseButtonEventHandler),\n\t\t\ttypeof(NotifyIcon));\n\n\t\tpublic static readonly DependencyProperty IconProperty = DependencyProperty.Register(\n\t\t\t\"Icon\",\n\t\t\ttypeof(ImageSource),\n\t\t\ttypeof(NotifyIcon),\n\t\t\tnew FrameworkPropertyMetadata(OnIconChanged));\n\n\t\tpublic static readonly DependencyProperty TextProperty = DependencyProperty.Register(\n\t\t\t\"Text\",\n\t\t\ttypeof(string),\n\t\t\ttypeof(NotifyIcon),\n\t\t\tnew PropertyMetadata(OnTextChanged));\n\n\t\tprivate Forms.NotifyIcon notifyIcon;\n\n\t\tstatic NotifyIcon()\n\t\t{\n\t\t\tVisibilityProperty.OverrideMetadata(typeof(NotifyIcon), new PropertyMetadata(OnVisibilityChanged));\n\t\t}\n\n\t\tpublic event MouseButtonEventHandler MouseClick\n\t\t{\n\t\t\tadd { this.AddHandler(MouseClickEvent, value); }\n\t\t\tremove { this.RemoveHandler(MouseClickEvent, value); }\n\t\t}\n\n\t\tpublic event MouseButtonEventHandler MouseDoubleClick\n\t\t{\n\t\t\tadd { this.AddHandler(MouseDoubleClickEvent, value); }\n\t\t\tremove { this.RemoveHandler(MouseDoubleClickEvent, value); }\n\t\t}\n\n\t\tpublic ImageSource Icon\n\t\t{\n\t\t\tget { return (ImageSource)this.GetValue(IconProperty); }\n\t\t\tset { this.SetValue(IconProperty, value); }\n\t\t}\n\n\t\tpublic string Text\n\t\t{\n\t\t\tget { return (string)this.GetValue(TextProperty); }\n\t\t\tset { this.SetValue(TextProperty, value); }\n\t\t}\n\n\t\tpublic override void BeginInit()\n\t\t{\n\t\t\tbase.BeginInit();\n\t\t\tthis.InitializeNotifyIcon();\n\t\t}\n\n\t\t#region IAddChild Members\n\n\t\tvoid IAddChild.AddChild(object value)\n\t\t{\n\t\t\tthrow new InvalidOperationException();\n\t\t}\n\n\t\tvoid IAddChild.AddText(string text)\n\t\t{\n\t\t\tif (text == null)\n\t\t\t{\n\t\t\t\tthrow new ArgumentNullException(nameof(text));\n\t\t\t}\n\n\t\t\tthis.Text = text;\n\t\t}\n\n\t\t#endregion\n\n\t\tprotected override void OnVisualParentChanged(DependencyObject oldParent)\n\t\t{\n\t\t\tbase.OnVisualParentChanged(oldParent);\n\t\t\tthis.AttachToWindowClose();\n\t\t}\n\n\t\tprivate static MouseButtonEventArgs CreateMouseButtonEventArgs(\n\t\t\tRoutedEvent handler,\n\t\t\tForms.MouseButtons button)\n\t\t{\n\t\t\treturn new MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, ToMouseButton(button))\n\t\t\t{\n\t\t\t\tRoutedEvent = handler\n\t\t\t};\n\t\t}\n\n\t\tprivate static Drawing.Icon? FromImageSource(ImageSource icon)\n\t\t{\n\t\t\tif (icon == null)\n\t\t\t{\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tUri iconUri = new Uri(icon.ToString());\n\t\t\treturn new Drawing.Icon(Application.GetResourceStream(iconUri).Stream);\n\t\t}\n\n\t\tprivate static void OnIconChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)\n\t\t{\n\t\t\tif (!DesignerProperties.GetIsInDesignMode(target))\n\t\t\t{\n\t\t\t\tNotifyIcon control = (NotifyIcon)target;\n\t\t\t\tcontrol.notifyIcon.Icon = FromImageSource(control.Icon);\n\t\t\t}\n\t\t}\n\n\t\tprivate static void OnTextChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)\n\t\t{\n\t\t\tNotifyIcon control = (NotifyIcon)target;\n\t\t\tcontrol.notifyIcon.Text = control.Text;\n\t\t}\n\n\t\tprivate static void OnVisibilityChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)\n\t\t{\n\t\t\tNotifyIcon control = (NotifyIcon)target;\n\t\t\tcontrol.notifyIcon.Visible = control.Visibility == Visibility.Visible;\n\t\t}\n\n\t\tprivate static MouseButton ToMouseButton(Forms.MouseButtons button)\n\t\t{\n\t\t\tswitch (button)\n\t\t\t{\n\t\t\t\tcase Forms.MouseButtons.Left:\n\t\t\t\t\treturn MouseButton.Left;\n\t\t\t\tcase Forms.MouseButtons.Right:\n\t\t\t\t\treturn MouseButton.Right;\n\t\t\t\tcase Forms.MouseButtons.Middle:\n\t\t\t\t\treturn MouseButton.Middle;\n\t\t\t\tcase Forms.MouseButtons.XButton1:\n\t\t\t\t\treturn MouseButton.XButton1;\n\t\t\t\tcase Forms.MouseButtons.XButton2:\n\t\t\t\t\treturn MouseButton.XButton2;\n\t\t\t}\n\n\t\t\tthrow new InvalidOperationException();\n\t\t}\n\n\t\tprivate void AttachToWindowClose()\n\t\t{\n\t\t\tvar window = Window.GetWindow(this);\n\t\t\tif (window != null)\n\t\t\t{\n\t\t\t\twindow.Closed += (s, a) => this.notifyIcon.Dispose();\n\t\t\t}\n\t\t}\n\n\t\tprivate void InitializeNotifyIcon()\n\t\t{\n\t\t\tthis.notifyIcon = new Forms.NotifyIcon();\n\t\t\tthis.notifyIcon.Text = this.Text;\n\t\t\tthis.notifyIcon.Icon = FromImageSource(this.Icon);\n\t\t\tthis.notifyIcon.Visible = this.Visibility == Visibility.Visible;\n\n\t\t\tthis.notifyIcon.MouseDown += this.OnMouseDown;\n\t\t\tthis.notifyIcon.MouseUp += this.OnMouseUp;\n\t\t\tthis.notifyIcon.MouseClick += this.OnMouseClick;\n\t\t\tthis.notifyIcon.MouseDoubleClick += this.OnMouseDoubleClick;\n\n\t\t\tthis.InitializeNativeHooks();\n\t\t}\n\n\t\tprivate void OnMouseDown(object sender, Forms.MouseEventArgs e)\n\t\t{\n\t\t\tthis.RaiseEvent(CreateMouseButtonEventArgs(MouseDownEvent, e.Button));\n\t\t}\n\n\t\tprivate void OnMouseDoubleClick(object sender, Forms.MouseEventArgs e)\n\t\t{\n\t\t\tthis.RaiseEvent(CreateMouseButtonEventArgs(MouseDoubleClickEvent, e.Button));\n\t\t}\n\n\t\tprivate void OnMouseClick(object sender, Forms.MouseEventArgs e)\n\t\t{\n\t\t\tthis.RaiseEvent(CreateMouseButtonEventArgs(MouseClickEvent, e.Button));\n\t\t}\n\n\t\tprivate void OnMouseUp(object sender, Forms.MouseEventArgs e)\n\t\t{\n\t\t\tif (e.Button == Forms.MouseButtons.Right)\n\t\t\t{\n\t\t\t\tthis.ShowContextMenu();\n\t\t\t}\n\n\t\t\tthis.RaiseEvent(CreateMouseButtonEventArgs(MouseUpEvent, e.Button));\n\t\t}\n\n\t\tprivate void ShowContextMenu()\n\t\t{\n\t\t\tif (this.ContextMenu != null)\n\t\t\t{\n\t\t\t\tthis.AttachContextMenu();\n\t\t\t\tthis.ContextMenu.IsOpen = true;\n\t\t\t}\n\t\t}\n\n\t\tpartial void AttachContextMenu();\n\n\t\tpartial void InitializeNativeHooks();\n\n\n\t}\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/Settings/AppPackageSettingsService.cs",
    "content": "﻿using System;\nusing Newtonsoft.Json;\nusing PresenceLight.Core;\nusing PresenceLight.Telemetry;\nusing System.Threading.Tasks;\nusing Windows.Storage;\nusing Microsoft.Extensions.Logging;\nusing System.IO;\nusing PresenceLight.Razor;\n\nnamespace PresenceLight.Services\n{\n    public class AppPackageSettingsService : ISettingsService\n    {\n        private const string SETTINGS_FILENAME = \"settings.json\";\n        private static readonly StorageFolder _settingsFolder = Windows.Storage.ApplicationData.Current.LocalFolder;\n        private DiagnosticsClient _diagClient;\n        private readonly ILogger<AppPackageSettingsService> _logger;\n        private readonly AppState _appState;\n\n        public AppPackageSettingsService(DiagnosticsClient diagClient, ILogger<AppPackageSettingsService> logger, AppState appState)\n        {\n            _appState = appState;\n            _logger = logger;\n            _diagClient = diagClient;\n        }\n\n        public async Task<BaseConfig?> LoadSettings()\n        {\n            try\n            {\n                StorageFile sf = await _settingsFolder.GetFileAsync(SETTINGS_FILENAME);\n                if (sf == null) return null;\n\n                string content = await FileIO.ReadTextAsync(sf, Windows.Storage.Streams.UnicodeEncoding.Utf8);\n                var config = JsonConvert.DeserializeObject<BaseConfig>(content);\n                _appState.SetConfig(config);\n                return config;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error saving Settings\");\n                _diagClient.TrackException(e);\n                return null;\n            }\n        }\n\n        public async Task<bool> SaveSettings(BaseConfig data)\n        {\n            try\n            {\n                string content = JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { });\n                StorageFile f;\n                if (await IsFilePresent())\n                {\n                    f = await _settingsFolder.GetFileAsync(SETTINGS_FILENAME);\n                }\n                else\n                {\n                    f = await _settingsFolder.CreateFileAsync(SETTINGS_FILENAME, CreationCollisionOption.ReplaceExisting);\n                }\n                bool fileWritten = false;\n\n                while (!fileWritten)\n                {\n                    try\n                    {\n                        await FileIO.WriteTextAsync(f, content, Windows.Storage.Streams.UnicodeEncoding.Utf8);\n                        fileWritten = true;\n                    }\n                    catch\n                    {                     \n                    }\n                }\n                _appState.SetConfig(data);\n                return true;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Saving Settings\");\n                _diagClient.TrackException(e);\n                return false;\n            }\n        }\n\n        public async Task<bool> DeleteSettings()\n        {\n            try\n            {\n                StorageFile sf = await _settingsFolder.GetFileAsync(SETTINGS_FILENAME);\n                var foo = sf.DeleteAsync(StorageDeleteOption.PermanentDelete);\n                return true;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Deleting Settings File\");\n                _diagClient.TrackException(e);\n                return false;\n            }\n        }\n\n        public async Task<bool> IsFilePresent()\n        {\n            try\n            {\n                var item = await _settingsFolder.TryGetItemAsync(SETTINGS_FILENAME);\n\n                if (item == null)\n                {\n                    return false;\n                }\n                else\n                {\n                    var config = await LoadSettings();\n                    if (config == null)\n                    {\n                        return false;\n                    }\n                }\n\n                return true;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Finding Settings File\");\n                _diagClient.TrackException(e);\n                return false;\n            }\n        }\n\n        public string GetSettingsFileLocation()\n        {\n            return BuildSettingsFileLocation();\n        }\n\n        public static string BuildSettingsFileLocation()\n        {\n            return Path.Combine(_settingsFolder.Path, SETTINGS_FILENAME);\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/Settings/StandaloneSettingsService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.IO;\nusing PresenceLight.Core;\nusing PresenceLight.Telemetry;\nusing Newtonsoft.Json;\nusing Microsoft.Extensions.Logging;\nusing PresenceLight.Razor;\n\nnamespace PresenceLight.Services\n{\n    public class StandaloneSettingsService : ISettingsService\n    {\n        private const string _settingsFileName = \"settings.json\";\n        private static readonly string _settingsFolder = Directory.GetCurrentDirectory();\n        private DiagnosticsClient _diagClient;\n        private readonly ILogger<StandaloneSettingsService> _logger;\n        private readonly AppState _appState;\n\n        public StandaloneSettingsService(DiagnosticsClient diagClient, ILogger<StandaloneSettingsService> logger, AppState appState)\n        {\n            _appState = appState;\n            _logger = logger;\n            _diagClient = diagClient;\n        }\n\n        public Task<bool> DeleteSettings()\n        {\n            if (File.Exists(GetSettingsFileLocation()))\n            {\n                File.Delete(GetSettingsFileLocation());\n            }\n            return Task.Run(() => true);\n        }\n\n        public async Task<bool> IsFilePresent()\n        {\n            try\n            {\n                if (!File.Exists(GetSettingsFileLocation()))\n                {\n                    return false;\n                }\n                else\n                {\n                    var config = await LoadSettings();\n                    if (config == null)\n                    {\n                        return false;\n                    }\n                }\n\n                return true;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Finding Settings File\");\n                _diagClient.TrackException(e);\n                return false;\n            }\n        }\n\n        public async Task<BaseConfig?> LoadSettings()\n        {\n            try\n            {\n                string fileJSON = await File.ReadAllTextAsync(GetSettingsFileLocation(), Encoding.UTF8);\n                var config = JsonConvert.DeserializeObject<BaseConfig>(fileJSON);\n                _appState.SetConfig(config);\n                return config;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Loading Settings\");\n                _diagClient.TrackException(e);\n                return null;\n            }\n        }\n\n        public async Task<bool> SaveSettings(BaseConfig data)\n        {\n            try\n            {\n                string content = JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { });\n                await File.WriteAllTextAsync(GetSettingsFileLocation(), content, Encoding.UTF8);\n                _appState.SetConfig(data);\n                return true;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error saving Settings\");\n                _diagClient.TrackException(e);\n                return false;\n            }\n        }\n\n        public string GetSettingsFileLocation()\n        {\n            return BuildSettingsFileLocation();\n        }\n\n        public static string BuildSettingsFileLocation()\n        {\n            return Path.Combine(_settingsFolder, _settingsFileName);\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/SingleInstanceAppMutex.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Services\n{\n    class SingleInstanceAppMutex\n    {\n        private static Mutex s_mutex;\n\n        public static bool TakeExclusivity()\n        {\n            var assembly = Assembly.GetExecutingAssembly();\n            var mutexName = $\"Local\\\\{assembly.GetName().Name}-0e510f7b-aed2-40b0-ad72-d2d3fdc89a02\";\n\n            s_mutex = new Mutex(true, mutexName, out bool mutexCreated);\n            if (!mutexCreated)\n            {\n                Trace.WriteLine(\"SingleInstanceAppMutex TakeExclusivity: false\");\n                s_mutex = null;\n                return false;\n            }\n            return true;\n        }\n\n        public static void ReleaseExclusivity()\n        {\n            s_mutex?.ReleaseMutex();\n            s_mutex?.Close();\n            s_mutex = null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/Telemetry/DiagnosticsClient.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nusing Microsoft.ApplicationInsights;\nusing Microsoft.ApplicationInsights.Extensibility;\n\nnamespace PresenceLight.Telemetry\n{\n    public class DiagnosticsClient\n    {\n        private TelemetryClient _client;\n\n\n        public DiagnosticsClient(TelemetryClient tc)\n        {\n            _client = tc;\n\n           \n            TrackEvent(\"AppStart\");\n            System.Windows.Application.Current.Exit += Application_Exit;\n            System.Windows.Application.Current.DispatcherUnhandledException += DispatcherUnhandledException;\n        }\n\n\n        private void DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)\n        {\n            TrackException(e.Exception);\n            e.Handled = true;\n        }\n\n        private void Application_Exit(object sender, System.Windows.ExitEventArgs e)\n        {\n            TrackEvent(\"AppExit\");\n            _client.Flush();\n            // Allow time for flushing:\n            System.Threading.Thread.Sleep(1000);\n        }\n\n        private void Application_Startup(object sender, System.Windows.StartupEventArgs e)\n        {\n            TrackEvent(\"AppStart\");\n        }\n\n        public void TrackEvent(string eventName, IDictionary<string, string>? properties = null, IDictionary<string, double>? metrics = null)\n        {\n            _client.TrackEvent(eventName, properties, metrics);\n        }\n\n        public void TrackTrace(string evt)\n        {\n            _client.TrackTrace(evt);\n        }\n\n        public void TrackException(Exception exception, IDictionary<string, string>? properties = null, IDictionary<string, double>? metrics = null)\n        {\n            _client.TrackException(exception, properties, metrics);\n        }\n\n        public void TrackPageView(string pageName)\n        {\n            _client.TrackPageView(pageName);\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/appsettings.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"StartMinimized\": false,\n  \"IconType\": \"White\",\n  \"AADSettings\": {\n    \"ClientId\": \"\",\n    \"TenantId\": \"common\",\n    \"Instance\": \"https://login.microsoftonline.com/\",\n    \"RedirectUri\": \"http://localhost\",\n    \"Scopes\": [\n      \"https://graph.microsoft.com/.default\"\n    ]\n  },\n  \"LightSettings\": {\n    \"HoursPassedStatus\": \"Keep\",\n    \"SyncLights\": true,\n    \"WorkingDays\": \"Monday|Tuesday|Wednesday|Thursday|Friday\",\n    \"WorkingHoursStartTime\": \"\",\n    \"WorkingHoursEndTime\": \"\",\n    \"UseAmPm\": true,\n    \"UseWorkingHours\": false,\n    \"PollingInterval\": 5.0,\n    \"UseDefaultBrightness\": true,\n    \"DefaultBrightness\": 100,\n    \"LIFX\": {\n      \"LIFXClientId\": \"\",\n      \"LIFXClientSecret\": \"\",\n      \"LIFXApiKey\": \"\",\n      \"IsEnabled\": false,\n      \"SelectedItemId\": \"\",\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"Statuses\": {\n        \"AvailabilityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#00FF55\"\n        },\n        \"AvailabilityAvailableIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF3300\"\n        },\n        \"AvailabilityBusyIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#B03CDE\"\n        },\n        \"AvailabilityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#4f824f\"\n        },\n        \"ActivityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"ActivityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#fFFf00\"\n        },\n        \"ActivityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF0000\"\n        },\n        \"ActivityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityInACallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF0000\"\n        },\n        \"ActivityInAConferenceCallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF00d4\"\n        },\n        \"ActivityInactiveStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF00\"\n        },\n        \"ActivityInAMeetingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF0000\"\n        },\n        \"ActivityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOffWorkStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOutOfOfficeStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ae00FF\"\n        },\n        \"ActivityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityPresentingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityUrgentInterruptionsOnlyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#560061\"\n        },\n        \"ActivityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        }\n      }\n    },\n    \"Hue\": {\n      \"HueApiKey\": \"\",\n      \"SelectedItemId\": \"\",\n      \"HueIpAddress\": \"\",\n      \"RemoteHueClientId\": \"\",\n      \"RemoteHueClientSecret\": \"\",\n      \"RemoteHueClientAppName\": \"\",\n      \"IsEnabled\": false,\n      \"RemoteBridgeId\": \"\",\n      \"UseRemoteApi\": false,\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"Statuses\": {\n        \"AvailabilityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#00FF55\"\n        },\n        \"AvailabilityAvailableIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF3300\"\n        },\n        \"AvailabilityBusyIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#B03CDE\"\n        },\n        \"AvailabilityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#4f824f\"\n        },\n        \"ActivityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityInACallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityInAConferenceCallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff00d4\"\n        },\n        \"ActivityInactiveStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityInAMeetingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOffWorkStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOutOfOfficeStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ae00ff\"\n        },\n        \"ActivityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityPresentingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityUrgentInterruptionsOnlyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#560061\"\n        },\n        \"ActivityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        }\n      }\n    },\n    \"Yeelight\": {\n      \"SelectedItemId\": \"\",\n      \"IsEnabled\": false,\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"Statuses\": {\n        \"AvailabilityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#00ff55\"\n        },\n        \"AvailabilityAvailableIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF3300\"\n        },\n        \"AvailabilityBusyIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#B03CDE\"\n        },\n        \"AvailabilityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#4f824f\"\n        },\n        \"ActivityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityInACallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityInAConferenceCallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff00d4\"\n        },\n        \"ActivityInactiveStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityInAMeetingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOffWorkStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOutOfOfficeStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ae00ff\"\n        },\n        \"ActivityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityPresentingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityUrgentInterruptionsOnlyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#560061\"\n        },\n        \"ActivityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        }\n      }\n    },\n    \"CustomApi\": {\n      \"IsEnabled\": false,\n      \"SelectedItemId\": \"\",\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"CustomApiAvailable\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiBusy\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiBeRightBack\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiAway\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiDoNotDisturb\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiOffline\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiOff\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityAvailable\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityInACall\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityInAConferenceCall\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityInAMeeting\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityPresenting\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityBusy\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityAway\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiAvailableIdle\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityBeRightBack\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityDoNotDisturb\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityIdle\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityOffline\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityOff\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityOffWork\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiTimeout\": 100,\n      \"IgnoreCertificateErrors\": false,\n      \"UseBasicAuth\": false,\n      \"BasicAuthUserName\": \"\",\n      \"BasicAuthUserPassword\": \"\"\n    },\n    \"LocalSerialHost\": {\n      \"IsEnabled\": false,\n      \"SelectedItemId\": \"\",\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"BaudRate\": \"\",\n      \"PortNumber\": \"\",\n      \"LocalSerialHostMainSetup\": {\n        \"BaudRate\": \"\",\n        \"LineEnding\": \"\",\n        \"Port\": \"\"\n      },\n      \"LocalSerialHostAvailable\": \"\",\n      \"LocalSerialHostBusy\": \"\",\n      \"LocalSerialHostBeRightBack\": \"\",\n      \"LocalSerialHostAway\": \"\",\n      \"LocalSerialHostDoNotDisturb\": \"\",\n      \"LocalSerialHostOffline\": \"\",\n      \"LocalSerialHostOff\": \"\",\n      \"LocalSerialHostActivityAvailable\": \"\",\n      \"LocalSerialHostActivityInACall\": \"\",\n      \"LocalSerialHostActivityInAConferenceCall\": \"\",\n      \"LocalSerialHostActivityInAMeeting\": \"\",\n      \"LocalSerialHostActivityPresenting\": \"\",\n      \"LocalSerialHostActivityBusy\": \"\",\n      \"LocalSerialHostActivityAway\": \"\",\n      \"LocalSerialHostAvailableIdle\": \"\",\n      \"LocalSerialHostActivityBeRightBack\": \"\",\n      \"LocalSerialHostActivityDoNotDisturb\": \"\",\n      \"LocalSerialHostActivityIdle\": \"\",\n      \"LocalSerialHostActivityOffline\": \"\",\n      \"LocalSerialHostActivityOff\": \"\"\n    },\n    \"Wiz\": {\n      \"SelectedItemId\": \"\",\n      \"Brightness\": 100,\n      \"IsEnabled\": false,\n      \"UseActivityStatus\": false,\n      \"Statuses\": {\n        \"AvailabilityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#00ff55\"\n        },\n        \"AvailabilityAvailableIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF3300\"\n        },\n        \"AvailabilityBusyIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#B03CDE\"\n        },\n        \"AvailabilityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#4f824f\"\n        },\n        \"ActivityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityInACallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityInAConferenceCallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff00d4\"\n        },\n        \"ActivityInactiveStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityInAMeetingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOffWorkStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOutOfOfficeStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ae00ff\"\n        },\n        \"ActivityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityPresentingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityUrgentInterruptionsOnlyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#560061\"\n        },\n        \"ActivityOFFStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        }\n      }\n    }\n  },\n  \"ApplicationInsights\": {\n    \"TelemetryChannel\": {\n      \"DeveloperMode\": false\n    },\n    \"InstrumentationKey\": \"\"\n  },\n  \"SnapshotCollectorConfiguration\": {\n    \"IsEnabledInDeveloperMode\": true,\n    \"ThresholdForSnapshotting\": 1,\n    \"MaximumSnapshotsRequired\": 3,\n    \"MaximumCollectionPlanSize\": 50,\n    \"ReconnectInterval\": \"00:15:00\",\n    \"ProblemCounterResetInterval\": \"1.00:00:00\",\n    \"SnapshotsPerTenMinutesLimit\": 1,\n    \"SnapshotsPerDayLimit\": 30,\n    \"SnapshotInLowPriorityThread\": true,\n    \"ProvideAnonymousTelemetry\": true,\n    \"FailedRequestLimit\": 3\n  },\n  \"Serilog\": {\n    \"Using\": [\n      \"Serilog.Sinks.Console\",\n      \"Serilog.Sinks.File\"\n    ],\n    \"MinimumLevel\": \"Information\",\n    \"WriteTo\": [\n      {\n        \"Name\": \"Console\"\n      },\n      {\n        \"Name\": \"File\",\n        \"Args\": {\n          \"path\": \"%LOCALAPPDATA%/PresenceLight/logs/DesktopClient/log-.json\",\n          \"formatter\": \"Serilog.Formatting.Json.JsonFormatter, Serilog\",\n          \"shared\": \"true\",\n          \"rollingInterval\": \"Hour\",\n          \"retainedFileCountLimit\": 24\n        }\n      }\n    ],\n    \"Enrich\": [\n      \"FromLogContext\",\n      \"WithThreadId\"\n    ],\n    \"Properties\": {\n      \"Application\": \"PresenceLight\"\n    }\n  },\n  \"AppType\": \"Desktop\",\n  \"AppVersion\": \"5.6.11\"\n}\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/wwwroot/index.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />\n    <title>Blazor WPF app</title>\n    <base href=\"/\" />\n    <link rel=\"stylesheet\" href=\"_content/PresenceLight.Razor/css/bootstrap/bootstrap.min.css\" />\n    <link rel=\"stylesheet\" href=\"_content/PresenceLight.Razor/css/bootstrap/bootstrap-big-grid.min.css\" />\n    <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.12.0/css/all.css\">\n    <link href=\"_content/PresenceLight.Razor/css/site.css\" rel=\"stylesheet\" />\n    <!-- inside of head section -->\n    <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css\" integrity=\"sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn\" crossorigin=\"anonymous\">\n    <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.15.4/css/all.css\">\n\n    <link href=\"_content/Blazorise/blazorise.css\" rel=\"stylesheet\" />\n    <link href=\"_content/Blazorise.Bootstrap/blazorise.bootstrap.css\" rel=\"stylesheet\" />\n    <link href=\"PresenceLight.styles.css\" rel=\"stylesheet\" />\n\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap\" rel=\"stylesheet\" />\n    <link href=\"_content/MudBlazor/MudBlazor.min.css\" rel=\"stylesheet\" />\n</head>\n\n<body>\n    <div id=\"app\"></div>\n\n    <div id=\"blazor-error-ui\">\n        An unhandled error has occurred.\n        <a href=\"\" class=\"reload\">Reload</a>\n        <a class=\"dismiss\">🗙</a>\n    </div>\n\n    <!-- inside of body section and after the div/app tag  -->\n    <script src=\"https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js\" integrity=\"sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj\" crossorigin=\"anonymous\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js\" integrity=\"sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN\" crossorigin=\"anonymous\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js\" integrity=\"sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2\" crossorigin=\"anonymous\"></script>\n\n    <link href=\"_content/BlazorPro.Spinkit/spinkit.min.css\" rel=\"stylesheet\" />\n\n    <script src=\"_content/PresenceLight.Razor/js/site.js\"></script>\n    <script src=\"_content/MudBlazor/MudBlazor.min.js\"></script>\n    <script src=\"_framework/blazor.webview.js\"></script>\n\n</body>\n\n</html>"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package-Local.appxmanifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<Package\n  xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\"\n  xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\"\n  xmlns:desktop=\"http://schemas.microsoft.com/appx/manifest/desktop/windows10\"\n  xmlns:rescap=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities\"\n  xmlns:uap3=\"http://schemas.microsoft.com/appx/manifest/uap/windows10/3\"\n  IgnorableNamespaces=\"uap rescap desktop\">\n\n  <Identity\n    Name=\"37828IsaacLevin.197278F15330A.Local\"\n    Publisher=\"CN=Presence Light, O=Isaac Levin, C=US\"\n    Version=\"1.0.0.0\" />\n\n  <Properties>\n    <DisplayName>PresenceLight (Local)</DisplayName>\n    <PublisherDisplayName>Isaac Levin</PublisherDisplayName>\n    <Logo>Images\\StoreLogo.png</Logo>\n  </Properties>\n\n  <Dependencies>\n    <TargetDeviceFamily Name=\"Windows.Desktop\" MinVersion=\"10.0.17134.0\" MaxVersionTested=\"10.0.18362.0\" />\n  </Dependencies>\n\n  <Resources>\n    <Resource Language=\"x-generate\" />\n  </Resources>\n\n  <Applications>\n    <Application Id=\"App\"\n      Executable=\"$targetnametoken$.exe\"\n      EntryPoint=\"$targetentrypoint$\">\n      <uap:VisualElements\n        DisplayName=\"PresenceLight (Local)\"\n        Description=\"PresenceLight\"\n        BackgroundColor=\"transparent\"\n        Square150x150Logo=\"Images\\Square150x150Logo.png\"\n        Square44x44Logo=\"Images\\Square44x44Logo.png\">\n        <uap:DefaultTile Wide310x150Logo=\"Images\\Wide310x150Logo.png\"  Square71x71Logo=\"Images\\SmallTile.png\" Square310x310Logo=\"Images\\LargeTile.png\">\n          <uap:ShowNameOnTiles>\n            <uap:ShowOn Tile=\"wide310x150Logo\"/>\n            <uap:ShowOn Tile=\"square310x310Logo\"/>\n            <uap:ShowOn Tile=\"square150x150Logo\"/>\n          </uap:ShowNameOnTiles>\n        </uap:DefaultTile >\n        <uap:SplashScreen Image=\"Images\\SplashScreen.png\"  BackgroundColor=\"transparent\"/>\n        <uap:LockScreen BadgeLogo=\"Images\\BadgeLogo.png\" Notification=\"badge\"/>\n      </uap:VisualElements>\n        <Extensions>\n            <desktop:Extension Category=\"windows.startupTask\" Executable=\"PresenceLight\\PresenceLight.exe\" EntryPoint=\"Windows.FullTrustApplication\">\n                <desktop:StartupTask TaskId=\"PresenceLight\" Enabled=\"true\" DisplayName=\"PresenceLight\" />\n            </desktop:Extension>\n        </Extensions>\n    </Application>\n  </Applications>\n\n  <Capabilities>\n    <Capability Name=\"internetClient\" />\n    <rescap:Capability Name=\"runFullTrust\" />\n  </Capabilities>\n</Package>"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package-Nightly.appxmanifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<Package\n  xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\"\n  xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\"\n  xmlns:desktop=\"http://schemas.microsoft.com/appx/manifest/desktop/windows10\"\n  xmlns:rescap=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities\"\n  xmlns:uap3=\"http://schemas.microsoft.com/appx/manifest/uap/windows10/3\"\n  IgnorableNamespaces=\"uap rescap desktop\">\n\n  <Identity\n    Name=\"37828IsaacLevin.197278F15330A.Nightly\"\n    Publisher=\"CN=Presence Light, O=Isaac Levin, C=US\"\n    Version=\"1.0.0.0\" />\n\n  <Properties>\n    <DisplayName>PresenceLight (Nightly)</DisplayName>\n    <PublisherDisplayName>Isaac Levin</PublisherDisplayName>\n    <Logo>Images\\StoreLogo.png</Logo>\n  </Properties>\n\n  <Dependencies>\n    <TargetDeviceFamily Name=\"Windows.Desktop\" MinVersion=\"10.0.17134.0\" MaxVersionTested=\"10.0.18362.0\" />\n  </Dependencies>\n\n  <Resources>\n    <Resource Language=\"x-generate\" />\n  </Resources>\n\n  <Applications>\n    <Application Id=\"App\"\n      Executable=\"PresenceLight\\PresenceLight.exe\"\n      EntryPoint=\"$targetentrypoint$\">\n      <uap:VisualElements\n        DisplayName=\"PresenceLight (Nightly)\"\n        Description=\"PresenceLight\"\n        BackgroundColor=\"transparent\"\n        Square150x150Logo=\"Images\\Square150x150Logo.png\"\n        Square44x44Logo=\"Images\\Square44x44Logo.png\">\n        <uap:DefaultTile Wide310x150Logo=\"Images\\Wide310x150Logo.png\"  Square71x71Logo=\"Images\\SmallTile.png\" Square310x310Logo=\"Images\\LargeTile.png\">\n          <uap:ShowNameOnTiles>\n            <uap:ShowOn Tile=\"wide310x150Logo\"/>\n            <uap:ShowOn Tile=\"square310x310Logo\"/>\n            <uap:ShowOn Tile=\"square150x150Logo\"/>\n          </uap:ShowNameOnTiles>\n        </uap:DefaultTile >\n        <uap:SplashScreen Image=\"Images\\SplashScreen.png\"  BackgroundColor=\"transparent\"/>\n        <uap:LockScreen BadgeLogo=\"Images\\BadgeLogo.png\" Notification=\"badge\"/>\n      </uap:VisualElements>\n      <Extensions>\n        <desktop:Extension Category=\"windows.startupTask\" Executable=\"PresenceLight\\PresenceLight.exe\" EntryPoint=\"Windows.FullTrustApplication\">\n          <desktop:StartupTask TaskId=\"PresenceLight\" Enabled=\"true\" DisplayName=\"PresenceLight\" />\n        </desktop:Extension>\n      </Extensions>\n    </Application>\n  </Applications>\n\n  <Capabilities>\n    <Capability Name=\"internetClient\" />\n    <rescap:Capability Name=\"runFullTrust\" />\n  </Capabilities>\n</Package>\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package.appinstaller",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<AppInstaller xmlns=\"http://schemas.microsoft.com/appx/appinstaller/2017/2\"\n              Uri=\"{AppInstallerUri}\"\n              Version=\"{Version}\">\n\n  <MainBundle Name=\"{Name}\"\n              Version=\"{Version}\"\n              Publisher=\"{Publisher}\"\n              Uri=\"{MainPackageUri}\"/>\n\n  <UpdateSettings>\n    <OnLaunch HoursBetweenUpdateChecks=\"4\" />\n    <AutomaticBackgroundTask/>\n  </UpdateSettings>\n</AppInstaller>"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package.appxmanifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<Package\n  xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\"\n  xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\"\n  xmlns:desktop=\"http://schemas.microsoft.com/appx/manifest/desktop/windows10\"\n  xmlns:rescap=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities\"\n  xmlns:uap3=\"http://schemas.microsoft.com/appx/manifest/uap/windows10/3\"\n  IgnorableNamespaces=\"uap rescap desktop\">\n\n    <Identity\n      Name=\"37828IsaacLevin.197278F15330A\"\n      Publisher=\"CN=82A4CBF8-0920-4FD3-B44C-A9D0D7BB5865\"\n      Version=\"1.0.0.0\" />\n\n    <Properties>\n        <DisplayName>PresenceLight</DisplayName>\n        <PublisherDisplayName>Isaac Levin</PublisherDisplayName>\n        <Logo>Images\\StoreLogo.png</Logo>\n    </Properties>\n\n    <Dependencies>\n        <TargetDeviceFamily Name=\"Windows.Desktop\" MinVersion=\"10.0.17134.0\" MaxVersionTested=\"10.0.18362.0\" />\n    </Dependencies>\n\n    <Resources>\n        <Resource Language=\"x-generate\" />\n    </Resources>\n\n    <Applications>\n        <Application Id=\"App\"\n          Executable=\"PresenceLight\\PresenceLight.exe\"\n          EntryPoint=\"$targetentrypoint$\">\n            <uap:VisualElements\n              DisplayName=\"PresenceLight\"\n              Description=\"PresenceLight\"\n              BackgroundColor=\"transparent\"\n              Square150x150Logo=\"Images\\Square150x150Logo.png\"\n              Square44x44Logo=\"Images\\Square44x44Logo.png\">\n                <uap:DefaultTile Wide310x150Logo=\"Images\\Wide310x150Logo.png\"  Square71x71Logo=\"Images\\SmallTile.png\" Square310x310Logo=\"Images\\LargeTile.png\">\n                    <uap:ShowNameOnTiles>\n                        <uap:ShowOn Tile=\"wide310x150Logo\"/>\n                        <uap:ShowOn Tile=\"square310x310Logo\"/>\n                        <uap:ShowOn Tile=\"square150x150Logo\"/>\n                    </uap:ShowNameOnTiles>\n                </uap:DefaultTile >\n                <uap:SplashScreen Image=\"Images\\SplashScreen.png\"  BackgroundColor=\"transparent\"/>\n                <uap:LockScreen BadgeLogo=\"Images\\BadgeLogo.png\" Notification=\"badge\"/>\n            </uap:VisualElements>\n            <Extensions>\n                <desktop:Extension Category=\"windows.startupTask\" Executable=\"PresenceLight\\PresenceLight.exe\" EntryPoint=\"Windows.FullTrustApplication\">\n                    <desktop:StartupTask TaskId=\"PresenceLight\" Enabled=\"true\" DisplayName=\"PresenceLight\" />\n                </desktop:Extension>\n            </Extensions>\n        </Application>\n    </Applications>\n\n    <Capabilities>\n        <Capability Name=\"internetClient\" />\n        <rescap:Capability Name=\"runFullTrust\" />\n    </Capabilities>\n</Package>\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<StoreAssociation xmlns=\"http://schemas.microsoft.com/appx/2010/storeassociation\">\n  <Publisher>CN=82A4CBF8-0920-4FD3-B44C-A9D0D7BB5865</Publisher>\n  <PublisherDisplayName>Isaac Levin</PublisherDisplayName>\n  <DeveloperAccountType>AAD</DeveloperAccountType>\n  <GeneratePackageHash>http://www.w3.org/2001/04/xmlenc#sha256</GeneratePackageHash>\n  <SupportedLocales>\n    <Language Code=\"af\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"af-za\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"am\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"am-et\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-ae\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-bh\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-dz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-eg\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-iq\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-jo\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-kw\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-lb\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-ly\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-ma\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-om\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-qa\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-sa\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-sy\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-tn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ar-ye\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"as\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"as-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"az\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"az-arab\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"az-arab-az\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"az-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"az-cyrl-az\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"az-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"az-latn-az\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"be\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"be-by\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bg\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bg-bg\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bn-bd\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bn-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bs\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bs-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bs-cyrl-ba\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bs-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"bs-latn-ba\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ca\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ca-es\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ca-es-valencia\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"chr-cher\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"chr-cher-us\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"chr-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"cs\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"cs-cz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"cy\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"cy-gb\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"da\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"da-dk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"de\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"de-at\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"de-ch\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"de-de\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"de-li\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"de-lu\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"el\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"el-gr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-011\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-014\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-018\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-021\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-029\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-053\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-au\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-bz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-ca\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-gb\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-hk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-id\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-ie\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-jm\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-kz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-mt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-my\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-nz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-ph\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-pk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-sg\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-tt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-us\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-vn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-za\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"en-zw\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-019\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-419\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-ar\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-bo\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-cl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-co\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-cr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-do\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-ec\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-es\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-gt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-hn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-mx\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-ni\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-pa\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-pe\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-pr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-py\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-sv\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-us\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-uy\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"es-ve\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"et\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"et-ee\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"eu\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"eu-es\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fa\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fa-ir\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fi\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fi-fi\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fil\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fil-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fil-ph\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-011\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-015\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-021\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-029\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-155\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-be\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-ca\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-cd\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-ch\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-ci\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-cm\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-fr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-ht\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-lu\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-ma\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-mc\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-ml\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"fr-re\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"frc-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"frp-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ga\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ga-ie\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"gd-gb\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"gd-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"gl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"gl-es\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"gu\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"gu-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ha\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ha-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ha-latn-ng\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"he\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"he-il\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hi\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hi-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hr-ba\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hr-hr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hu\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hu-hu\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hy\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"hy-am\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"id\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"id-id\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ig-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ig-ng\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"is\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"is-is\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"it\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"it-ch\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"it-it\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"iu-cans\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"iu-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"iu-latn-ca\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ja\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ja-jp\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ka\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ka-ge\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"kk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"kk-kz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"km\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"km-kh\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"kn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"kn-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ko\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ko-kr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"kok\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"kok-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ku-arab\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ku-arab-iq\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ky-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ky-kg\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"lb\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"lb-lu\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"lo\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"lo-la\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"lt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"lt-lt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"lv\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"lv-lv\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mi\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mi-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mi-nz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mk-mk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ml\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ml-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mn-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mn-mn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mn-mong\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mn-phag\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mr-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ms\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ms-bn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ms-my\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"mt-mt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nb\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nb-no\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ne\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ne-np\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nl-be\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nl-nl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nn-no\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"no\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"no-no\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nso\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"nso-za\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"or\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"or-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pa\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pa-arab\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pa-arab-pk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pa-deva\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pa-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pl-pl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"prs\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"prs-af\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"prs-arab\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pt-br\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"pt-pt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"quc-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"qut-gt\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"qut-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"quz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"quz-bo\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"quz-ec\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"quz-pe\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ro\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ro-ro\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ru\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ru-ru\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"rw\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"rw-rw\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sd-arab\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sd-arab-pk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sd-deva\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"si\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"si-lk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sk-sk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sl-si\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sq\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sq-al\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-cyrl-ba\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-cyrl-cs\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-cyrl-me\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-cyrl-rs\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-latn-ba\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-latn-cs\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-latn-me\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sr-latn-rs\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sv\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sv-fi\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sv-se\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sw\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"sw-ke\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ta\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ta-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"te\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"te-in\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tg-arab\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tg-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tg-cyrl-tj\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tg-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"th\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"th-th\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ti\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ti-et\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tk-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tk-cyrl-tr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tk-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tk-latn-tr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tk-tm\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tn-bw\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tn-za\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tr-tr\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tt-arab\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tt-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tt-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"tt-ru\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ug-arab\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ug-cn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ug-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ug-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"uk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"uk-ua\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ur\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"ur-pk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"uz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"uz-cyrl\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"uz-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"uz-latn-uz\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"vi\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"vi-vn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"wo\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"wo-sn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"xh\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"xh-za\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"yo-latn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"yo-ng\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-cn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-hans\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-hans-cn\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-hans-sg\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-hant\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-hant-hk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-hant-mo\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-hant-tw\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-hk\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-mo\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-sg\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zh-tw\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zu\" InMinimumRequirementSet=\"true\" />\n    <Language Code=\"zu-za\" InMinimumRequirementSet=\"true\" />\n  </SupportedLocales>\n  <ProductReservedInfo>\n    <MainPackageIdentityName>37828IsaacLevin.197278F15330A</MainPackageIdentityName>\n    <ReservedNames>\n      <ReservedName>PresenceLight</ReservedName>\n    </ReservedNames>\n  </ProductReservedInfo>\n</StoreAssociation>"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/PresenceLight.Package.wapproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup Condition=\"'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'\">\n    <VisualStudioVersion>15.0</VisualStudioVersion>\n  </PropertyGroup>\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Debug|x86\">\n      <Configuration>Debug</Configuration>\n      <Platform>x86</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup>\n    <Platform Condition=\" '$(Platform)' == '' \">x64</Platform>\n    <WapProjPath Condition=\"'$(WapProjPath)'==''\">$(MSBuildExtensionsPath)\\Microsoft\\DesktopBridge\\</WapProjPath>\n  </PropertyGroup>\n  <Import Project=\"$(WapProjPath)\\Microsoft.DesktopBridge.props\" />\n  <PropertyGroup>\n    <ProjectGuid>12ddad24-ccbd-409f-9342-17a0e445604f</ProjectGuid>\n    <TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>\n    <TargetPlatformMinVersion>10.0.17134.0</TargetPlatformMinVersion>\n    <DefaultLanguage>en</DefaultLanguage>\n    <DebuggerType>ManagedOnly</DebuggerType>\n    <AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>\n    <AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>\n    <AppxBundleAutoResourcePackageQualifiers>Language|DXFeatureLevel</AppxBundleAutoResourcePackageQualifiers>\n    <AppxBundle>Always</AppxBundle>\n    <AppxBundlePlatforms>x64|x86|ARM64</AppxBundlePlatforms>\n    <AppxPackageIncludePrivateSymbols>true</AppxPackageIncludePrivateSymbols>\n    <UapAppxPackageBuildMode>SideloadOnly</UapAppxPackageBuildMode>\n    <UapAppxPackageBuildMode Condition=\"'$(ChannelName)' == 'Release'\">StoreUpload</UapAppxPackageBuildMode>\n    <EntryPointProjectUniqueName>..\\PresenceLight\\PresenceLight.csproj</EntryPointProjectUniqueName>\n    <EntryPointExe Condition=\"'$(BuildingInsideVisualStudio)' != 'true' \">PresenceLight\\PresenceLight.exe</EntryPointExe>\n    <EntryPointProjectUniqueName Condition=\"'$(BuildingInsideVisualStudio)' == 'true' \">..\\PresenceLight\\PresenceLight.csproj</EntryPointProjectUniqueName>\n    <GetPackagingOutputsDependsOn Condition=\"'$(BuildingInsideVisualStudio)' != 'true' \">$(GetPackagingOutputsDependsOn);BuildRefsOutputGroup</GetPackagingOutputsDependsOn>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(ChannelName)' == 'Nightly'\">\n    <GenerateAppInstallerFile>True</GenerateAppInstallerFile>\n    <AppInstallerUri>https://presencelight.blob.core.windows.net/nightly</AppInstallerUri>\n    <AppInstallerUpdateFrequency>0</AppInstallerUpdateFrequency>\n    <AppInstallerCheckForUpdateFrequency>OnApplicationRun</AppInstallerCheckForUpdateFrequency>\n  </PropertyGroup>\n  <ItemGroup>\n    <AppxManifest Include=\"Package.appxmanifest\" Condition=\"'$(ChannelName)' == 'Release'\">\n      <SubType>Designer</SubType>\n    </AppxManifest>\n    <AppxManifest Include=\"Package-Nightly.appxmanifest\" Condition=\"'$(ChannelName)' == 'Nightly'\">\n      <SubType>Designer</SubType>\n    </AppxManifest>\n    <AppxManifest Include=\"Package-Local.appxmanifest\" Condition=\"'$(ChannelName)' != 'Release' and '$(ChannelName)' != 'Nightly'\">\n      <SubType>Designer</SubType>\n    </AppxManifest>\n    <None Include=\"*.appxmanifest\" />\n    <None Include=\"*.appinstaller\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Content Include=\"Images\\*.png\" />\n    <None Include=\"Package.xml\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"'$(BuildingInsideVisualStudio)' == 'true'\">\n    <ProjectReference Include=\"..\\PresenceLight\\PresenceLight.csproj\">\n      <SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>\n      <PublishProfile Condition=\"'$(Platform)'=='x64'\">Properties\\PublishProfiles\\WinX64.pubxml</PublishProfile>\n      <PublishProfile Condition=\"'$(Platform)'=='ARM64'\">Properties\\PublishProfiles\\WinARM64.pubxml</PublishProfile>\n      <PublishProfile Condition=\"'$(Platform)'=='x86'\">Properties\\PublishProfiles\\WinX86.pubxml</PublishProfile>\n    </ProjectReference>\n  </ItemGroup>\n  <ItemGroup>\n    <PackageReference Update=\"Microsoft.SourceLink.GitHub\" Version=\"8.0.0\" />\n    <PackageReference Update=\"Nerdbank.GitVersioning\" Version=\"3.6.133\" />\n    <PackageReference Include=\"System.Private.Uri\" Version=\"4.3.2\" />\n  </ItemGroup>\n  <Import Project=\"$(WapProjPath)\\Microsoft.DesktopBridge.targets\" />\n  <Target Name=\"BuildRef\" Condition=\"'$(BuildingInsideVisualStudio)' != 'true' \" BeforeTargets=\"BeforeBuild\">\n    <PropertyGroup Condition=\"'$(OS)' == 'Windows_NT'\">\n      <NpeExecCmd>dotnet publish -c $(Configuration) \"$(MSBuildThisFileDirectory)..\\PresenceLight\\PresenceLight.csproj\" /p:PublishProfile=Properties\\PublishProfiles\\Win$(Platform).pubxml</NpeExecCmd>\n    </PropertyGroup>\n    <Message Text=\"Executing: $(NpeExecCmd)\" Importance=\"High\" />\n    <Exec Command=\"$(NpeExecCmd)\" />\n    <Message Text=\"Built ref \" />\n    <ItemGroup>\n      <TheFiles Include=\"..\\PresenceLight\\bin\\$(Configuration)\\net10.0-windows10.0.19041\\win-$(Platform)\\publish\\**\\*.*\" />\n      <TheFiles TargetPath=\"PresenceLight\\%(RecursiveDir)%(Filename)%(Extension)\" />\n      <File Include=\"@(TheFiles)\">\n        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n      </File>\n    </ItemGroup>\n  </Target>\n  <Target Name=\"BuildRefsOutputGroup\" Returns=\"@(BuiltRefsOutput)\">\n    <ItemGroup>\n      <_PackagingOutputsUnexpanded Include=\"@(BuiltRef)\" OutputGroup=\"BuildRefsOutputGroup\" ProjectName=\"$(ProjectName)\" />\n    </ItemGroup>\n  </Target>\n</Project>\n"
  },
  {
    "path": "src/DockerCompose/.dockerignore",
    "content": "**/.classpath\n**/.dockerignore\n**/.env\n**/.git\n**/.gitignore\n**/.project\n**/.settings\n**/.toolstarget\n**/.vs\n**/.vscode\n**/*.*proj.user\n**/*.dbmdl\n**/*.jfm\n**/azds.yaml\n**/bin\n**/charts\n**/docker-compose*\n**/Dockerfile*\n**/node_modules\n**/npm-debug.log\n**/obj\n**/secrets.dev.yaml\n**/values.dev.yaml\nLICENSE\nREADME.md"
  },
  {
    "path": "src/DockerCompose/docker-compose.dcproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" Sdk=\"Microsoft.Docker.Sdk\">\n  <PropertyGroup Label=\"Globals\">\n    <ProjectVersion>2.1</ProjectVersion>\n    <DockerTargetOS>Linux</DockerTargetOS>\n    <ProjectGuid>d6871e74-a6e2-41ee-aa4a-7be357501d63</ProjectGuid>\n    <DockerLaunchAction>LaunchBrowser</DockerLaunchAction>\n    <DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>\n    <DockerServiceName>presencelight.web</DockerServiceName>\n    <DockerDevelopmentMode>Regular</DockerDevelopmentMode>\n  </PropertyGroup>\n  <ItemGroup>\n    <None Include=\"docker-compose.override.yml\">\n      <DependentUpon>docker-compose.yml</DependentUpon>\n    </None>\n    <None Include=\"docker-compose.yml\" />\n    <None Include=\".dockerignore\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "src/DockerCompose/docker-compose.override.yml",
    "content": "version: '3.4'\n\nservices:\n  presencelight.web:\n    environment:\n      - ASPNETCORE_ENVIRONMENT=Development\n      - ASPNETCORE_URLS=https://+:443;http://+:80\n    ports:\n      - \"5000:80\"\n      - \"5001:443\"\n    volumes:\n      - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro\n      - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro"
  },
  {
    "path": "src/DockerCompose/docker-compose.yml",
    "content": "version: '3.4'\n\nservices:\n  presencelight.web:\n    image: ${DOCKER_REGISTRY-}presencelightweb\n    build:\n      context: ..\n      dockerfile: PresenceLight.Web/Dockerfile\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/AAD.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Text.Json.Serialization;\n\n\nusing Newtonsoft.Json;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the Azure Active Directory (AAD) settings.\n    /// </summary>\n    public class AADSettings\n    {\n        /// <summary>\n        /// Gets or sets the client ID for AAD authentication.\n        /// </summary>\n        public string? ClientId { get; set; }\n\n        /// <summary>\n        /// Gets or sets the tenant ID for AAD authentication.\n        /// </summary>\n        public string? TenantId { get; set; }\n\n        /// <summary>\n        /// Gets or sets the Client Secret for AAD authentication.\n        /// </summary>\n        public string? ClientSecret { get; set; }\n        /// <summary>\n        /// Gets or sets the AAD instance URL.\n        /// </summary>\n        public string? Instance { get; set; }\n\n        /// <summary>\n        /// Gets or sets the redirect URI for AAD authentication.\n        /// </summary>\n        public string? RedirectUri { get; set; }\n\n        /// <summary>\n        /// Gets or sets the redirect host for AAD authentication.\n        /// </summary>\n        public string? RedirectHost { get; set; }\n\n        /// <summary>\n        /// Gets or sets the CallbackPath for AAD authentication.\n        /// </summary>\n        public string? CallbackPath { get; set; }\n\n        /// <summary>\n        /// Gets or sets the Scopes for AAD authentication.\n        /// </summary>\n        public List<string>? Scopes { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/AppState.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nusing Microsoft.Graph;\nusing Microsoft.Graph.Models;\n\nusing PresenceLight.Core.WizServices;\n\nusing Device = YeelightAPI.Device;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the application state.\n    /// </summary>\n    public class AppState\n    {\n        /// <summary>\n        /// Event that is triggered when the state changes.\n        /// </summary>\n        public event Action OnChange;\n\n        /// <summary>\n        /// Gets or sets the user information.\n        /// </summary>\n        public User User { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the user is signed in.\n        /// </summary>\n        public bool SignedIn { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether the AAD config is complete.\n        /// </summary>\n        public bool AadConfigComplete { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether a sign-in request has been made.\n        /// </summary>\n        public bool SignInRequested { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether a sign-out request has been made.\n        /// </summary>\n        public bool SignOutRequested { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether a rebuild request has been made.\n        /// </summary>\n        public bool RebuildRequested { get; set; }\n\n        /// <summary>\n        /// Gets or sets the list of Hue lights.\n        /// </summary>\n        public IEnumerable<object> HueLights { get; set; }\n\n        /// <summary>\n        /// Gets or sets the selected Hue light ID.\n        /// </summary>\n        public string HueLightId { get; set; }\n\n        /// <summary>\n        /// Gets or sets the list of Yeelight lights.\n        /// </summary>\n        public List<Device> YeelightLights { get; set; }\n\n        /// <summary>\n        /// Gets or sets the selected Yeelight light ID.\n        /// </summary>\n        public string YeelightLightId { get; set; }\n\n        /// <summary>\n        /// Gets or sets the list of local serial hosts.\n        /// </summary>\n        public IEnumerable<string> LocalSerialHosts { get; set; }\n\n        /// <summary>\n        /// Gets or sets the selected local serial host.\n        /// </summary>\n        public string LocalSerialHostSelected { get; set; }\n\n        /// <summary>\n        /// Gets or sets the list of LIFX lights.\n        /// </summary>\n        public IEnumerable<object> LIFXLights { get; set; }\n\n        /// <summary>\n        /// Gets or sets the selected LIFX light ID.\n        /// </summary>\n        public string LIFXLightId { get; set; }\n\n        /// <summary>\n        /// Gets or sets the Wiz light.\n        /// </summary>\n        public WizLight WizLight { get; set; }\n\n\n        /// <summary>\n        /// Gets or sets the profile image URL.\n        /// </summary>\n        public string ProfileImage { get; set; }\n\n        /// <summary>\n        /// Gets or sets the presence information.\n        /// </summary>\n        public Presence Presence { get; set; }\n\n        /// <summary>\n        /// Gets or sets the light mode.\n        /// </summary>\n        public string LightMode { get; set; }\n\n        /// <summary>\n        /// Gets or sets the custom color.\n        /// </summary>\n        public string CustomColor { get; set; }\n\n        /// <summary>\n        /// Gets or sets the base configuration.\n        /// </summary>\n        public BaseConfig Config { get; set; } = new BaseConfig();\n\n        /// <summary>\n        /// Sets the configuration.\n        /// </summary>\n        /// <param name=\"config\">The configuration to set.</param>\n        public void SetConfig(BaseConfig config)\n        {\n            Config = config;\n        }\n\n        /// <summary>\n        /// Sets the user information.\n        /// </summary>\n        /// <param name=\"user\">The user information.</param>\n        /// <param name=\"presence\">The presence information.</param>\n        /// <param name=\"photo\">The profile image URL.</param>\n        public void SetUserInfo(User? user, Presence? presence, string? photo = null)\n        {\n            User = user;\n            Presence = presence;\n            ProfileImage = photo;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the presence information.\n        /// </summary>\n        /// <param name=\"presence\">The presence information.</param>\n        public void SetPresence(Presence presence)\n        {\n            Presence = presence;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the custom color.\n        /// </summary>\n        /// <param name=\"color\">The custom color.</param>\n        public void SetCustomColor(string color)\n        {\n            CustomColor = color;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the light mode.\n        /// </summary>\n        /// <param name=\"lightMode\">The light mode.</param>\n        public void SetLightMode(string lightMode)\n        {\n            LightMode = lightMode;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the list of Hue lights.\n        /// </summary>\n        /// <param name=\"lights\">The list of Hue lights.</param>\n        public void SetHueLights(IEnumerable<object> lights)\n        {\n            HueLights = lights;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the selected Hue light ID.\n        /// </summary>\n        /// <param name=\"lightId\">The selected Hue light ID.</param>\n        public void SetHueLight(string lightId)\n        {\n            HueLightId = lightId;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the list of Yeelight lights.\n        /// </summary>\n        /// <param name=\"lights\">The list of Yeelight lights.</param>\n        public void SetYeelightLights(List<Device> lights)\n        {\n            YeelightLights = lights;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the selected Yeelight light ID.\n        /// </summary>\n        /// <param name=\"lightId\">The selected Yeelight light ID.</param>\n        public void SetYeelightLight(string lightId)\n        {\n            YeelightLightId = lightId;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the list of LIFX lights.\n        /// </summary>\n        /// <param name=\"lights\">The list of LIFX lights.</param>\n        public void SetLIFXLights(IEnumerable<object> lights)\n        {\n            LIFXLights = lights;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the selected LIFX light ID.\n        /// </summary>\n        /// <param name=\"lightId\">The selected LIFX light ID.</param>\n        public void SetLIFXLight(string lightId)\n        {\n            LIFXLightId = lightId;\n            NotifyStateChanged();\n        }\n\n\n        /// <summary>\n        /// Sets the list of local serial hosts.\n        /// </summary>\n        /// <param name=\"lights\">The list of local serial hosts.</param>\n        public void SetLocalSerialHosts(IEnumerable<string> lights)\n        {\n            LocalSerialHosts = lights;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the selected local serial host.\n        /// </summary>\n        /// <param name=\"port\">The selected local serial host.</param>\n        public void SetLocalSerialHost(string port)\n        {\n            LocalSerialHostSelected = port;\n            NotifyStateChanged();\n        }\n\n        /// <summary>\n        /// Sets the selected Wiz light.\n        /// </summary>\n        /// <param name=\"light\">The selected Wiz light.</param>\n        public void SetWizLight(WizLight light)\n        {\n            WizLight = light;\n            NotifyStateChanged();\n        }\n\n        private void NotifyStateChanged() => OnChange?.Invoke();\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/AvailabilityStatus.cs",
    "content": "﻿namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the availability status configuration.\n    /// </summary>\n    public class AvailabilityStatus\n    {\n        /// <summary>\n        /// Gets or sets a value indicating whether the availability status is disabled.\n        /// </summary>\n        public bool Disabled { get; set; }\n\n        /// <summary>\n        /// Gets or sets the color associated with the availability status.\n        /// </summary>\n        public string? Color { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Base.cs",
    "content": "﻿namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the base configuration for the application.\n    /// </summary>\n    public class BaseConfig\n    {\n        /// <summary>\n        /// Gets or sets a value indicating whether the application should start minimized.\n        /// </summary>\n        public bool StartMinimized { get; set; }\n\n        /// <summary>\n        /// Gets or sets the type of icon to be used.\n        /// </summary>\n        public string? IconType { get; set; }\n\n        /// <summary>\n        /// Gets or sets the light settings for the application.\n        /// </summary>\n        public LightSettings LightSettings { get; set; }\n\n        /// <summary>\n        /// Gets or sets the type of application.\n        /// </summary>\n        public string AppType { get; set; }\n\n\n        /// <summary>\n        /// Gets or sets the Microsoft Entra Settings.\n        /// </summary>\n        public AADSettings AADSettings { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/BaseLight.cs",
    "content": "﻿namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents a base light configuration.\n    /// </summary>\n    public class BaseLight\n    {\n        /// <summary>\n        /// Gets or sets a value indicating whether the light is enabled.\n        /// </summary>\n        public bool IsEnabled { get; set; }\n\n        /// <summary>\n        /// Gets or sets the selected item ID.\n        /// </summary>\n        public string? SelectedItemId { get; set; }\n\n        /// <summary>\n        /// Gets or sets the brightness level of the light.\n        /// </summary>\n        public int Brightness { get; set; }\n\n        /// <summary>\n        /// Gets or sets the presence light statuses.\n        /// </summary>\n        public PresenceLightStatuses Statuses { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether to use activity status for the light.\n        /// </summary>\n        public bool UseActivityStatus { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/CustomApi.cs",
    "content": "﻿namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the configuration settings for a custom API.\n    /// </summary>\n    public class CustomApi : BaseLight\n    {\n        /// <summary>\n        /// Gets or sets the timeout value for the custom API.\n        /// </summary>\n        public double CustomApiTimeout { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether to ignore certificate errors for the custom API.\n        /// </summary>\n        public bool IgnoreCertificateErrors { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether to use basic authentication for the custom API.\n        /// </summary>\n        public bool UseBasicAuth { get; set; }\n\n        /// <summary>\n        /// Gets or sets the username for basic authentication for the custom API.\n        /// </summary>\n        public string BasicAuthUserName { get; set; }\n\n        /// <summary>\n        /// Gets or sets the password for basic authentication for the custom API.\n        /// </summary>\n        public string BasicAuthUserPassword { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Available\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiAvailable { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Busy\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiBusy { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Be Right Back\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiBeRightBack { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Away\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiAway { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Do Not Disturb\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiDoNotDisturb { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Available Idle\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiAvailableIdle { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Offline\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiOffline { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Off\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiOff { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Available\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityAvailable { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: In a Call\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityInACall { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: In a Conference Call\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityInAConferenceCall { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: In a Meeting\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityInAMeeting { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Presenting\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityPresenting { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Busy\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityBusy { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Away\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityAway { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Be Right Back\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityBeRightBack { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Do Not Disturb\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityDoNotDisturb { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Idle\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityIdle { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Offline\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityOffline { get; set; }\n\n        /// <summary>\n        /// Gets or sets the configuration settings for the \"Activity: Off\" status of the custom API.\n        /// </summary>\n        public CustomApiSetting CustomApiActivityOff { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/CustomApiSetting.cs",
    "content": "﻿namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the settings for a custom API.\n    /// </summary>\n    public class CustomApiSetting\n    {\n        /// <summary>\n        /// Gets or sets the HTTP method used for the API.\n        /// </summary>\n        public string? Method { get; set; }\n\n        /// <summary>\n        /// Gets or sets the URI of the API.\n        /// </summary>\n        public string? Uri { get; set; }\n\n        /// <summary>\n        /// Gets or sets the Body of the API.\n        /// </summary>\n        public string? Body { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Hue.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nusing Newtonsoft.Json;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the configuration for Hue lights.\n    /// </summary>\n    public class Hue : BaseLight\n    {\n        /// <summary>\n        /// Gets or sets the client ID for remote Hue access.\n        /// </summary>\n        public string? RemoteHueClientId { get; set; }\n\n        /// <summary>\n        /// Gets or sets the client application name for remote Hue access.\n        /// </summary>\n        public string? RemoteHueClientAppName { get; set; }\n\n        /// <summary>\n        /// Gets or sets the client secret for remote Hue access.\n        /// </summary>\n        public string? RemoteHueClientSecret { get; set; }\n\n        /// <summary>\n        /// Gets or sets the API key for local Hue access.\n        /// </summary>\n        public string? HueApiKey { get; set; }\n\n        /// <summary>\n        /// Gets or sets the IP address of the Hue bridge.\n        /// </summary>\n        [Required]\n        [RegularExpression(@\"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b\",\n        ErrorMessage = \"Not a valid IP Address\")]\n        public string? HueIpAddress { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether to use remote API for Hue access.\n        /// </summary>\n        public bool UseRemoteApi { get; set; }\n\n        /// <summary>\n        /// Gets or sets the bridge ID for remote Hue access.\n        /// </summary>\n        public string RemoteBridgeId { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/ISettingsService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing PresenceLight.Core;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents a service for managing application settings.\n    /// </summary>\n    public interface ISettingsService\n    {\n        /// <summary>\n        /// Loads the application settings from a file.\n        /// </summary>\n        /// <returns>The loaded settings, or null if the file is not found.</returns>\n        public Task<BaseConfig?> LoadSettings();\n\n        /// <summary>\n        /// Saves the application settings to a file.\n        /// </summary>\n        /// <param name=\"data\">The settings to save.</param>\n        /// <returns>True if the settings are successfully saved, false otherwise.</returns>\n        public Task<bool> SaveSettings(BaseConfig data);\n\n        /// <summary>\n        /// Deletes the application settings file.\n        /// </summary>\n        /// <returns>True if the settings file is successfully deleted, false otherwise.</returns>\n        public Task<bool> DeleteSettings();\n\n        /// <summary>\n        /// Checks if the application settings file is present.\n        /// </summary>\n        /// <returns>True if the settings file is present, false otherwise.</returns>\n        public Task<bool> IsFilePresent();\n\n        /// <summary>\n        /// Gets the location of the application settings file.\n        /// </summary>\n        /// <returns>The file location.</returns>\n        public string GetSettingsFileLocation();\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/LIFX.cs",
    "content": "﻿namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the configuration for LIFX lights.\n    /// </summary>\n    public class LIFX : BaseLight\n    {\n        /// <summary>\n        /// Gets or sets the LIFX API key.\n        /// </summary>\n        public string? LIFXApiKey { get; set; }\n\n        /// <summary>\n        /// Gets or sets the LIFX client ID.\n        /// </summary>\n        public string? LIFXClientId { get; set; }\n\n        /// <summary>\n        /// Gets or sets the LIFX client secret.\n        /// </summary>\n        public string? LIFXClientSecret { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/LightSettings.cs",
    "content": "﻿using System;\n\nusing Newtonsoft.Json;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the settings for controlling the lights.\n    /// </summary>\n    public class LightSettings\n    {\n        /// <summary>\n        /// Gets or sets the status to display after a certain number of hours have passed.\n        /// </summary>\n        public string HoursPassedStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether to synchronize the lights.\n        /// </summary>\n        public bool SyncLights { get; set; }\n\n        /// <summary>\n        /// Gets or sets the working days.\n        /// </summary>\n        public string WorkingDays { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether to use working hours.\n        /// </summary>\n        public bool UseWorkingHours { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether to use AM/PM format for working hours.\n        /// </summary>\n        public bool UseAmPm { get; set; }\n\n        /// <summary>\n        /// Gets or sets the start time of working hours.\n        /// </summary>\n        public string WorkingHoursStartTime { get; set; }\n\n        /// <summary>\n        /// Gets or sets the start time of working hours as a <see cref=\"DateTime\"/> object.\n        /// </summary>\n        [Newtonsoft.Json.JsonIgnore]\n        [System.Text.Json.Serialization.JsonIgnore]\n        [JsonProperty(Required = Required.Default)]\n        public DateTime? WorkingHoursStartTimeAsDate { get; set; }\n\n        /// <summary>\n        /// Gets or sets the end time of working hours as a <see cref=\"DateTime\"/> object.\n        /// </summary>\n        [Newtonsoft.Json.JsonIgnore]\n        [System.Text.Json.Serialization.JsonIgnore]\n        [JsonProperty(Required = Required.Default)]\n        public DateTime? WorkingHoursEndTimeAsDate { get; set; }\n\n        /// <summary>\n        /// Gets or sets the end time of working hours.\n        /// </summary>\n        public string WorkingHoursEndTime { get; set; }\n\n        /// <summary>\n        /// Gets or sets the polling interval in seconds.\n        /// </summary>\n        public double PollingInterval { get; set; }\n\n        /// <summary>\n        /// Gets or sets a value indicating whether to use the default brightness.\n        /// </summary>\n        public bool UseDefaultBrightness { get; set; }\n\n        /// <summary>\n        /// Gets or sets the default brightness level.\n        /// </summary>\n        public int DefaultBrightness { get; set; }\n\n        /// <summary>\n        /// Gets or sets the custom API settings.\n        /// </summary>\n        public CustomApi CustomApi { get; set; }\n\n        /// <summary>\n        /// Gets or sets the local serial host settings.\n        /// </summary>\n        public LocalSerialHost LocalSerialHost { get; set; }\n\n        /// <summary>\n        /// Gets or sets the LIFX settings.\n        /// </summary>\n        public LIFX LIFX { get; set; }\n\n        /// <summary>\n        /// Gets or sets the Hue settings.\n        /// </summary>\n        public Hue Hue { get; set; }\n\n        /// <summary>\n        /// Gets or sets the Yeelight settings.\n        /// </summary>\n        public Yeelight Yeelight { get; set; }\n\n        /// <summary>\n        /// Gets or sets the Wiz settings.\n        /// </summary>\n        public Wiz Wiz { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/LocalSerialHost.cs",
    "content": "﻿namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the configuration settings for a local serial host.\n    /// </summary>\n    public class LocalSerialHost : BaseLight\n    {\n        /// <summary>\n        /// Gets or sets the main setup for the local serial host.\n        /// </summary>\n        public LocalSerialHostSetting LocalSerialHostMainSetup { get; set; }\n\n        /// <summary>\n        /// Gets or sets the available status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostAvailable { get; set; }\n\n        /// <summary>\n        /// Gets or sets the busy status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostBusy { get; set; }\n\n        /// <summary>\n        /// Gets or sets the be right back status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostBeRightBack { get; set; }\n\n        /// <summary>\n        /// Gets or sets the away status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostAway { get; set; }\n\n        /// <summary>\n        /// Gets or sets the do not disturb status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostDoNotDisturb { get; set; }\n\n        /// <summary>\n        /// Gets or sets the available idle status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostAvailableIdle { get; set; }\n\n        /// <summary>\n        /// Gets or sets the offline status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostOffline { get; set; }\n\n        /// <summary>\n        /// Gets or sets the off status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostOff { get; set; }\n\n        /// <summary>\n        /// Gets or sets the available activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityAvailable { get; set; }\n\n        /// <summary>\n        /// Gets or sets the in a call activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityInACall { get; set; }\n\n        /// <summary>\n        /// Gets or sets the in a conference call activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityInAConferenceCall { get; set; }\n\n        /// <summary>\n        /// Gets or sets the in a meeting activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityInAMeeting { get; set; }\n\n        /// <summary>\n        /// Gets or sets the presenting activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityPresenting { get; set; }\n\n        /// <summary>\n        /// Gets or sets the busy activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityBusy { get; set; }\n\n        /// <summary>\n        /// Gets or sets the away activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityAway { get; set; }\n\n        /// <summary>\n        /// Gets or sets the be right back activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityBeRightBack { get; set; }\n\n        /// <summary>\n        /// Gets or sets the do not disturb activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityDoNotDisturb { get; set; }\n\n        /// <summary>\n        /// Gets or sets the idle activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityIdle { get; set; }\n\n        /// <summary>\n        /// Gets or sets the offline activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityOffline { get; set; }\n\n        /// <summary>\n        /// Gets or sets the off activity status for the local serial host.\n        /// </summary>\n        public string LocalSerialHostActivityOff { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/LocalSerialHostSetting.cs",
    "content": "﻿namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the settings for a local serial host.\n    /// </summary>\n    public class LocalSerialHostSetting\n    {\n        /// <summary>\n        /// Gets or sets the baud rate for the serial communication.\n        /// </summary>\n        public string? BaudRate { get; set; }\n\n        /// <summary>\n        /// Gets or sets the line ending characters for the serial communication.\n        /// </summary>\n        public string? LineEnding { get; set; }\n\n        /// <summary>\n        /// Gets or sets the port name for the serial communication.\n        /// </summary>\n        public string? Port { get; set; }\n\n        /// <summary>\n        /// Gets or sets the message to be sent over the serial communication.\n        /// </summary>\n        public string? Message { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Statuses.cs",
    "content": "﻿using System;\nusing System.ComponentModel.DataAnnotations;\nusing System.Text.Json.Serialization;\n\nusing Newtonsoft.Json;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the statuses for availability and activity.\n    /// </summary>\n    public class PresenceLightStatuses\n    {\n        /// <summary>\n        /// Gets or sets the availability status for \"Available\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityAvailableStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Available (Idle)\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityAvailableIdleStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Away\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityAwayStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Be Right Back\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityBeRightBackStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Busy\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityBusyStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Busy (Idle)\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityBusyIdleStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Do Not Disturb\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityDoNotDisturbStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Offline\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityOfflineStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Presence Unknown\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityPresenceUnknownStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the availability status for \"Off\".\n        /// </summary>\n        public AvailabilityStatus AvailabilityOffStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Available\".\n        /// </summary>\n        public AvailabilityStatus ActivityAvailableStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Away\".\n        /// </summary>\n        public AvailabilityStatus ActivityAwayStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Be Right Back\".\n        /// </summary>\n        public AvailabilityStatus ActivityBeRightBackStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Busy\".\n        /// </summary>\n        public AvailabilityStatus ActivityBusyStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Do Not Disturb\".\n        /// </summary>\n        public AvailabilityStatus ActivityDoNotDisturbStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"In a Call\".\n        /// </summary>\n        public AvailabilityStatus ActivityInACallStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"In a Conference Call\".\n        /// </summary>\n        public AvailabilityStatus ActivityInAConferenceCallStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Inactive\".\n        /// </summary>\n        public AvailabilityStatus ActivityInactiveStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"In a Meeting\".\n        /// </summary>\n        public AvailabilityStatus ActivityInAMeetingStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Offline\".\n        /// </summary>\n        public AvailabilityStatus ActivityOfflineStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Off\".\n        /// </summary>\n        public AvailabilityStatus ActivityOffStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Off Work\".\n        /// </summary>\n        public AvailabilityStatus ActivityOffWorkStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Out of Office\".\n        /// </summary>\n        public AvailabilityStatus ActivityOutOfOfficeStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Presence Unknown\".\n        /// </summary>\n        public AvailabilityStatus ActivityPresenceUnknownStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Presenting\".\n        /// </summary>\n        public AvailabilityStatus ActivityPresentingStatus { get; set; }\n\n        /// <summary>\n        /// Gets or sets the activity status for \"Urgent Interruptions Only\".\n        /// </summary>\n        public AvailabilityStatus ActivityUrgentInterruptionsOnlyStatus { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Wiz.cs",
    "content": "﻿using System.ComponentModel.DataAnnotations;\n\nnamespace PresenceLight.Core\n{\n    public class Wiz : BaseLight\n    {\n        [Required]\n        [RegularExpression(@\"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b\", ErrorMessage = \"Not a valid IP Address\")]\n        public string? IPAddress { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Yeelight.cs",
    "content": "﻿using System;\nusing System.Text.Json.Serialization;\n\nnamespace PresenceLight.Core\n{\n\n    public class Yeelight : BaseLight\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/AuthorizationProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing Microsoft.Graph;\nusing Microsoft.Identity.Client;\nusing Microsoft.Identity.Client.Extensions.Msal;\nusing Microsoft.Kiota.Abstractions;\nusing Microsoft.Kiota.Abstractions.Authentication;\n\nnamespace PresenceLight.Core\n{\n    public class AuthorizationProvider : IAuthenticationProvider\n    {\n        public IPublicClientApplication PubClient { get; set; }\n        public IConfidentialClientApplication ConfClient { get; set; }\n        \n        private readonly ILogger<AuthorizationProvider> _logger;\n        private readonly IOptionsMonitor<BaseConfig> _configMonitor;\n        private readonly IDisposable? _reloadSubscription;\n\n        private readonly object _sync = new();\n        \n        public IAccount UserAccount { get; set; }\n\n        public AuthorizationProvider(IOptionsMonitor<BaseConfig> configMonitor, ILogger<AuthorizationProvider> logger)\n        {\n            _logger = logger;\n            _configMonitor = configMonitor;\n        }\n    \n        public bool RebuildMsalClients()\n        {\n            lock (_sync)\n            {\n                BaseConfig config = _configMonitor.CurrentValue;\n                if (config.AppType == \"Desktop\")\n                {\n                    if (!Helpers.AreStringsNotEmpty(new string[] {\n                        config.AADSettings.ClientId,\n                        config.AADSettings.TenantId,\n                        config.AADSettings.Instance,\n                        config.AADSettings.RedirectUri }))\n                    {\n                        _logger.LogWarning(\"One or more of ClientId, TenantId, Instance, or RedirectUri is not set.\");\n                        PubClient = null;\n                        return false;\n                    }\n\n                    PubClient = PublicClientApplicationBuilder.Create(config.AADSettings.ClientId)\n                        .WithAuthority($\"{config.AADSettings.Instance}{config.AADSettings.TenantId}/\")\n                        .WithRedirectUri(config.AADSettings.RedirectUri)\n                        .Build();\n\n                    TokenCacheHelper.EnableSerialization(PubClient.UserTokenCache);\n                    return true;\n                }\n                else if (config.AppType == \"Web\")\n                {\n                    if (!Helpers.AreStringsNotEmpty(new string[] {\n                        config.AADSettings.ClientId,\n                        config.AADSettings.ClientSecret,\n                        config.AADSettings.Instance,\n                        config.AADSettings.RedirectHost,\n                        config.AADSettings.CallbackPath }))\n                    {\n                        _logger.LogWarning(\"One or more of ClientId, ClientSecret, Instance, RedirectUri, or CallbackPath is not set.\");\n                        ConfClient = null;\n                        return false;\n                    }\n\n                    ConfClient = ConfidentialClientApplicationBuilder\n                        .Create(config.AADSettings.ClientId)\n                        .WithClientSecret(config.AADSettings.ClientSecret)\n                        .WithAuthority($\"{config.AADSettings.Instance}{config.AADSettings.TenantId}/v2.0\")\n                        .WithRedirectUri($\"{config.AADSettings.RedirectHost}{config.AADSettings.CallbackPath}\")\n                        .Build();\n\n                    var cacheHelper = CreateCacheHelperAsync(config.AADSettings.ClientId);\n                    cacheHelper.RegisterCache(ConfClient.UserTokenCache);\n                    return true;\n                }\n            }\n\n            return false;\n        }\n\n        \n\n        public void Invalidate()\n        {\n            lock (_sync)\n            {\n                PubClient = null;\n                ConfClient = null;\n                UserAccount = null;\n            }\n        }\n\n\n        public async Task<string> AcquireToken()\n        {\n            AuthenticationResult authResult = null;\n            string accessToken = null;\n            BaseConfig config = _configMonitor.CurrentValue;\n            if (config.AppType == \"Desktop\")\n            {\n                var accounts = await PubClient.GetAccountsAsync();\n                var firstAccount = accounts.FirstOrDefault();\n\n                try\n                {\n                    _logger.LogTrace(\"Acquiring token silently\");\n                    authResult = await PubClient.AcquireTokenSilent(config.AADSettings.Scopes, accounts.FirstOrDefault())\n                    .ExecuteAsync();\n\n                    UserAccount = authResult.Account;\n                    accessToken = authResult.AccessToken;\n                    _logger.LogDebug(\"Got tokens silently\");\n                }\n                catch (MsalUiRequiredException)\n                {\n                    _logger.LogInformation(\"Silent token acquisition failed. Falling back to interactive.\");\n                    try\n                    {\n                        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(120));\n                        //without a timeout, this hangs indefinitely if the browser/tab is closed\n                        authResult = await PubClient.AcquireTokenInteractive(config.AADSettings.Scopes)\n                           .WithUseEmbeddedWebView(false)\n                           .ExecuteAsync(cts.Token);\n                        _logger.LogTrace(\"Getting tokens interactively\");\n                        UserAccount = authResult.Account;\n                        _logger.LogDebug(\"Got user account\");\n                        accessToken = authResult.AccessToken;\n                        _logger.LogDebug(\"Got tokens interactively\");\n                    }\n                    catch (MsalException ex) when (ex.ErrorCode == \"access_denied\")\n                    {\n                        // User closed the browser / cancelled login\n                        _logger.LogWarning(\"User canceled interactive login\");\n                    }\n                    catch (MsalException ex) when (ex.ErrorCode == \"consent_required\")\n                    {\n                        _logger.LogWarning(\"User did not consent to the application\");\n                    }\n                    catch (MsalException ex) when (ex.ErrorCode == \"invalid_request\")\n                    {\n                        if (ex.Message != null && ex.Message.Contains(\"not configured as a multi-tenant application\"))\n                        {\n                            _logger.LogWarning(\"Application is not configured as multi-tenant\");\n                        }\n                        else\n                        {\n                            _logger.LogWarning(ex, \"Interactive token acquisition failed\");    \n                        }\n                    }\n                    catch (OperationCanceledException)\n                    {\n                        _logger.LogWarning(\"Interactive login canceled or timed out\");\n                    }\n                    catch (Exception ex)\n                    {\n                        _logger.LogWarning(ex, \"Interactive token acquisition failed\");\n                    }\n                }\n            }\n            else if (config.AppType == \"Web\")\n            {\n\n                try\n                {\n                    var result = await ConfClient\n                        .AcquireTokenSilent(config.AADSettings.Scopes, UserAccount)\n                        .ExecuteAsync();\n\n                    UserAccount = result.Account;\n                    accessToken = result.AccessToken;\n                }\n                catch (System.Exception)\n                { }\n            }\n\n            return accessToken;\n        }\n\n        private static MsalCacheHelper CreateCacheHelperAsync(string clientId)\n        {\n            StorageCreationProperties storageProperties;\n\n            try\n            {\n                storageProperties =\n                    new StorageCreationPropertiesBuilder(\n                    \"cache.plaintext\",\n                    System.AppContext.BaseDirectory,\n                    clientId)\n                    .WithLinuxUnprotectedFile()\n                    .Build();\n\n                var cacheHelper = MsalCacheHelper.CreateAsync(storageProperties).Result;\n                return cacheHelper;\n\n            }\n            catch (MsalCachePersistenceException e)\n            {\n                storageProperties =\n                    new StorageCreationPropertiesBuilder(\n                    \"cache.plaintext\",\n                    System.AppContext.BaseDirectory,\n                    clientId)\n                    .WithLinuxUnprotectedFile()\n                    .Build();\n\n                var cacheHelper = MsalCacheHelper.CreateAsync(storageProperties).Result;\n\n                cacheHelper.VerifyPersistence();\n\n                return cacheHelper;\n            }\n        }\n\n        async Task IAuthenticationProvider.AuthenticateRequestAsync(RequestInformation request, Dictionary<string, object>? additionalAuthenticationContext, CancellationToken cancellationToken)\n        {\n            string accessToken = await AcquireToken();\n\n            if (!string.IsNullOrEmpty(accessToken))\n            {\n                request.Headers.Add(\"Authorization\", $\"Bearer {accessToken}\");\n            }\n        }\n\n        public static bool AadChanged(BaseConfig config, BaseConfig newConfig)\n        {\n            if (config.AppType != newConfig.AppType)\n            {\n                return true;\n            }\n            else if (config.AppType == \"Desktop\")\n            {\n                bool aadChanged =\n                       config.AADSettings.ClientId != newConfig.AADSettings.ClientId ||\n                       config.AADSettings.TenantId != newConfig.AADSettings.TenantId ||\n                       config.AADSettings.Instance != newConfig.AADSettings.Instance ||\n                       config.AADSettings.RedirectUri != newConfig.AADSettings.RedirectUri;\n                return aadChanged;\n            }\n            else if (config.AppType == \"Web\")\n            {\n                bool aadChanged =\n                       config.AADSettings.ClientId != newConfig.AADSettings.ClientId ||\n                       config.AADSettings.TenantId != newConfig.AADSettings.TenantId ||\n                       config.AADSettings.Instance != newConfig.AADSettings.Instance ||\n                       config.AADSettings.CallbackPath != newConfig.AADSettings.CallbackPath ||\n                       config.AADSettings.RedirectHost != newConfig.AADSettings.RedirectHost ||\n                       config.AADSettings.ClientSecret != newConfig.AADSettings.ClientSecret;\n                return aadChanged;\n            }\n\n            return false;\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetIsInitialized/GetIsInitializedCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetIsInitializedCommand : IRequest<bool>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetIsInitialized/GetIsInitializedHandler.cs",
    "content": "﻿using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    internal class GetIsInitializedHandler : IRequestHandler<GetIsInitializedCommand, bool>\n    {\n        GraphWrapper _graph;\n\n        public GetIsInitializedHandler(GraphWrapper graph)\n        {\n            _graph = graph;\n        }\n\n\n        public async Task<bool> Handle(GetIsInitializedCommand command, CancellationToken cancellationToken)\n        {\n            return await Task.FromResult(_graph.IsInitialized);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetPhoto/GetPhotoCommand.cs",
    "content": "﻿using MediatR;\nusing System.IO;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetPhotoCommand : IRequest<Stream>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetPhoto/GetPhotoHandler.cs",
    "content": "﻿using MediatR;\nusing Microsoft.Graph;\nusing Polly.Retry;\nusing System.IO;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    internal class GetPhotoHandler : IRequestHandler<GetPhotoCommand, Stream>\n    {\n        GraphWrapper _graph;\n\n        public GetPhotoHandler(GraphWrapper graph)\n        {\n            _graph = graph;\n        }\n\n        public async Task<Stream> Handle(GetPhotoCommand command, CancellationToken cancellationToken)\n        {\n            return await _graph.GetPhoto(cancellationToken);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetPresence/GetPresenceCommand.cs",
    "content": "﻿using MediatR;\nusing Microsoft.Graph.Models;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetPresenceCommand : IRequest<Presence>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetPresence/GetPresenceHandler.cs",
    "content": "﻿using MediatR;\n\nusing Microsoft.Graph.Models;\n\nusing Polly.Retry;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    internal class GetPresenceHandler : IRequestHandler<GetPresenceCommand, Presence>\n    {\n        GraphWrapper _graph;\n\n        public GetPresenceHandler(GraphWrapper graph)\n        {\n            _graph = graph;\n        }\n\n        public async Task<Presence> Handle(GetPresenceCommand command, CancellationToken cancellationToken)\n        {\n            return await _graph.GetPresence(cancellationToken);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetProfile/GetProfileCommand.cs",
    "content": "﻿using MediatR;\n\nusing Microsoft.Graph.Models;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetProfileCommand : IRequest<User>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetProfile/GetProfileHandler.cs",
    "content": "﻿using MediatR;\n\nusing Microsoft.Graph.Models;\n\nusing Polly.Retry;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    internal class GetProfileHandler : IRequestHandler<GetProfileCommand, User>\n    {\n\n        GraphWrapper _graph;\n\n        public GetProfileHandler(GraphWrapper graph)\n        {\n            _graph = graph;\n        }\n\n\n        public async Task<User> Handle(GetProfileCommand command, CancellationToken cancellationToken)\n        {\n            return await _graph.GetProfile(cancellationToken);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetProfileAndPresence/GetProfileAndPresenceCommand.cs",
    "content": "﻿using MediatR;\n\nusing Microsoft.Graph.Models;\n\nusing System;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetProfileAndPresenceCommand : IRequest<(User User, Presence Presence)>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetProfileAndPresence/GetProfileAndPresenceHandler.cs",
    "content": "﻿using MediatR;\n\nusing Microsoft.Graph.Models;\n\nusing Polly;\nusing Polly.Retry;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    internal class GetProfileAndPresenceHandler : IRequestHandler<GetProfileAndPresenceCommand, (User User, Presence Presence)>\n    {\n        GraphWrapper _graph;\n\n        public GetProfileAndPresenceHandler(GraphWrapper graph)\n        {\n            _graph = graph;\n        }\n\n        public async Task<(User User, Presence Presence)> Handle(GetProfileAndPresenceCommand command, CancellationToken cancellationToken)\n        {\n           return await  _graph.GetProfileAndPresence(cancellationToken);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GraphWrapper.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Graph;\nusing Microsoft.Graph.Models;\nusing Microsoft.Graph.Models.ODataErrors;\n\nusing Polly;\nusing Polly.Retry;\n\nnamespace PresenceLight.Core\n{\n\n    public class GraphWrapper\n    {\n        private readonly ILogger<GraphWrapper> _logger;\n        private GraphServiceClient _graphServiceClient;\n        internal AsyncRetryPolicy _retryPolicy;\n        private LoginService loginService;\n\n        public bool IsInitialized\n        {\n            get\n            {\n                if (loginService != null)\n                {\n                    return loginService.IsInitialized;\n                }\n                return false;\n            }\n        }\n\n        public GraphWrapper(ILogger<GraphWrapper> logger, LoginService _loginService)\n        {\n            _logger = logger;\n            loginService = _loginService;\n\n            _retryPolicy = Policy\n                      .Handle<Exception>()\n                      .WaitAndRetryAsync(2, retryAttempt =>\n                      {\n                          var timeToWait = TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));\n                          return timeToWait;\n                      }\n                      );\n        }\n\n        public async Task Initialize()\n        {\n            await loginService.GetAuthenticatedGraphClient();\n            _graphServiceClient = loginService.GraphServiceClient;\n        }\n\n        public async Task<Presence> GetPresence(CancellationToken token)\n        {\n            try\n            {\n                return await _retryPolicy.ExecuteAsync<Presence>(async () => await _graphServiceClient.Me.Presence.GetAsync().ConfigureAwait(true));\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error Occurred Getting Presence from Graph Api Content\");\n                throw;\n            }\n        }\n\n\n        public async Task<System.IO.Stream> GetPhoto(CancellationToken token)\n        {\n            try\n            {\n                return await _retryPolicy.ExecuteAsync<Stream>(async () => await _graphServiceClient.Me.Photo.Content.GetAsync().ConfigureAwait(true));\n            }\n            catch (ODataError ex)\n            {\n                if (\"ImageNotFound\".Equals(ex.Error.Code))\n                {\n                    _logger.LogInformation(ex, \"Profile photo does not exist\");\n                    return null;\n                }\n                else\n                {\n                    throw;\n                }\n            }\n        }\n\n        public async Task<User> GetProfile(CancellationToken token)\n        {\n            try\n            {\n                return await _retryPolicy.ExecuteAsync<User>(async () => await _graphServiceClient.Me.GetAsync().ConfigureAwait(true));\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Getting Profile from Graph Api\");\n                throw;\n            }\n        }\n\n        public async Task<(User User, Presence Presence)> GetProfileAndPresence(CancellationToken token)\n        {\n            return await _retryPolicy.ExecuteAsync<(User User, Presence Presence)>(async () => await GetBatchContent(token));\n        }\n\n        private async Task<(User User, Presence Presence)> GetBatchContent(CancellationToken token)\n        {\n\n            _logger.LogInformation(\"Getting Graph Data: Profile, Image, Presence\");\n            try\n            {\n                var userRequest = _graphServiceClient.Me.ToGetRequestInformation();\n                var presenceRequest = _graphServiceClient.Me.Presence.ToGetRequestInformation();\n\n                BatchRequestContentCollection batchRequestContent = new BatchRequestContentCollection(_graphServiceClient);\n\n                var userRequestId = await batchRequestContent.AddBatchRequestStepAsync(userRequest);\n                var presenceRequestId = await batchRequestContent.AddBatchRequestStepAsync(presenceRequest);\n\n                var returnedResponse = await _graphServiceClient.Batch.PostAsync(batchRequestContent);\n\n                var user = await returnedResponse.GetResponseByIdAsync<User>(userRequestId);\n\n                var presence = await returnedResponse.GetResponseByIdAsync<Presence>(presenceRequestId);\n\n                return (User: user, Presence: presence);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Getting Batch Content from Graph Api\");\n                throw;\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/Initialize/InitializeCommand.cs",
    "content": "﻿using MediatR;\n\nusing Microsoft.Graph;\n\nusing System;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class InitializeCommand : IRequest\n    {\n        //public GraphServiceClient Client { get;   set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/Initialize/InitializeHandler.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\n\nusing MediatR;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    internal class InitializeHandler : IRequestHandler<InitializeCommand>\n    {\n        GraphWrapper _graph;\n\n        public InitializeHandler(GraphWrapper graph)\n        {\n            _graph = graph;\n        }\n\n        async Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand command, CancellationToken cancellationToken)\n        {\n            await _graph.Initialize();\n            await Task.CompletedTask;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/LoginService.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing Microsoft.Graph;\n\nnamespace PresenceLight.Core\n{\n    public class LoginService\n    {\n        public GraphServiceClient GraphServiceClient { get; set; }\n        \n        private readonly AuthorizationProvider _authProvider;\n        private readonly ILogger<LoginService> _logger;\n        private readonly IOptionsMonitor<BaseConfig> _configMonitor;\n        private readonly IDisposable? _reloadSubscription;\n        private readonly AppState _appState;\n        private readonly object _sync = new();\n        public bool IsInitialized { get; set; }\n        private BaseConfig config;\n\n        public LoginService(IOptionsMonitor<BaseConfig> configMonitor, AuthorizationProvider authProvider, AppState appState, ILogger<LoginService> logger)\n        {\n            _authProvider = authProvider;\n            _appState = appState;\n            _logger = logger;\n            _configMonitor = configMonitor;\n            config = _configMonitor.CurrentValue;\n            _appState.AadConfigComplete = _authProvider.RebuildMsalClients();\n\n            _reloadSubscription = _configMonitor.OnChange(async newConfig =>\n            {\n                try\n                {\n                    if (AuthorizationProvider.AadChanged(config, newConfig))\n                    {\n                        _logger?.LogInformation(\"AAD settings changed; signing out and invalidating auth provider.\");\n                        if (_appState.SignInRequested)\n                        {\n                            _appState.SignInRequested = false;\n                        }\n                        _appState.RebuildRequested = true;\n                    }\n                }\n                catch (Exception ex)\n                {\n                    _logger?.LogWarning(ex, \"Error reacting to AAD settings change.\");\n                }\n\n                config = newConfig;\n            });\n        }\n\n        public async Task GetAuthenticatedGraphClient()\n        {\n            await _authProvider.AcquireToken();\n            if (_authProvider.UserAccount != null)\n            {\n                GraphServiceClient = new GraphServiceClient(_authProvider);\n                IsInitialized = true;\n            }\n        }\n\n        public async Task<bool> IsUserAuthenticated()\n        {\n            BaseConfig config = _configMonitor.CurrentValue;\n            // If we already have the user account we're\n            // authenticated\n            if (_authProvider.UserAccount != null)\n            {\n                return true;\n            }\n\n            if (config.AppType == \"Desktop\")\n            {\n                if (_authProvider.PubClient == null)\n                {\n                    return false;\n                }\n\n                var accounts = await _authProvider.PubClient.GetAccountsAsync();\n\n                _authProvider.UserAccount = accounts.FirstOrDefault();\n                return null != _authProvider.UserAccount;\n            }\n            else if (config.AppType == \"Web\")\n            {\n                if (_authProvider.ConfClient == null)\n                {\n                    return false;\n                }\n\n                var accounts = await _authProvider.ConfClient.GetAccountsAsync();\n\n                _authProvider.UserAccount = accounts.FirstOrDefault();\n                return null != _authProvider.UserAccount;\n            }\n            return false;\n        }\n\n        public async Task<string> AddUserToTokenCache(string authorizationCode)\n        {\n            BaseConfig config = _configMonitor.CurrentValue;\n            var result = await _authProvider.ConfClient\n                .AcquireTokenByAuthorizationCode(config.AADSettings.Scopes, authorizationCode)\n                .ExecuteAsync();\n\n            _authProvider.UserAccount = result.Account;\n\n            return result.IdToken;\n        }\n\n        public async Task SignOut()\n        {\n            BaseConfig config = _configMonitor.CurrentValue;\n            if (config.AppType == \"Desktop\")\n            {\n                if (_authProvider.UserAccount != null)\n                {\n                    await _authProvider.PubClient.RemoveAsync(_authProvider.UserAccount);\n                }\n            }\n            else if (config.AppType == \"Web\")\n            {\n                if (_authProvider.UserAccount != null)\n                {\n                    await _authProvider.ConfClient.RemoveAsync(_authProvider.UserAccount);\n                }\n            }\n            _authProvider.UserAccount = null;\n            IsInitialized = false;\n            GraphServiceClient = null;\n        }\n\n        public void RebuildClient()\n        {\n            _authProvider.Invalidate();\n            _appState.AadConfigComplete = _authProvider.RebuildMsalClients();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/TokenCacheHelper.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Security.Cryptography;\nusing Microsoft.Identity.Client;\n\nnamespace PresenceLight.Core\n{\n    static class TokenCacheHelper\n    {\n        public static void EnableSerialization(ITokenCache tokenCache)\n        {\n            tokenCache.SetBeforeAccess(BeforeAccessNotification);\n            tokenCache.SetAfterAccess(AfterAccessNotification);\n        }\n\n        /// <summary>\n        /// Path to the token cache. Note that this could be something different for instance for MSIX applications:\n        /// </summary>\n        private static readonly string CacheFolderPath = $\"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\\\PresenceLight\\\\\";\n        private static readonly string CacheFileName = \"msalcache.bin\";\n\n        private static readonly object FileLock = new object();\n\n\n        private static void BeforeAccessNotification(TokenCacheNotificationArgs args)\n        {\n            lock (FileLock)\n            {\n                args.TokenCache.DeserializeMsalV3(File.Exists($\"{CacheFolderPath}{CacheFileName}\")\n                        ? ProtectedData.Unprotect(File.ReadAllBytes($\"{CacheFolderPath}{CacheFileName}\"),\n                                                  null,\n                                                  DataProtectionScope.CurrentUser)\n                        : null);\n            }\n        }\n\n        private static void AfterAccessNotification(TokenCacheNotificationArgs args)\n        {\n            // if the access operation resulted in a cache update\n            if (args.HasStateChanged)\n            {\n                lock (FileLock)\n                {\n                    // reflect changesgs in the persistent store\n\n                    if (!Directory.Exists(CacheFolderPath))\n                    {\n                        Directory.CreateDirectory(CacheFolderPath);\n                    }\n\n                    File.WriteAllBytes($\"{CacheFolderPath}{CacheFileName}\",\n                                        ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),\n                                                                null,\n                                                                DataProtectionScope.CurrentUser)\n                                        );\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Helpers.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing System.Text.RegularExpressions;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the status of hours passed.\n    /// </summary>\n    public enum HoursPassedStatus\n    {\n        Off,\n        Keep,\n        White\n    }\n\n    /// <summary>\n    /// Provides helper methods for various operations.\n    /// </summary>\n    public static class Helpers\n    {\n\n        public static bool AreStringsNotEmpty(string[] strings)\n        {\n            bool result = true;\n\n            foreach (var s in strings)\n            {\n                result = result && !string.IsNullOrEmpty(s);\n            }\n\n            return result;\n        }\n\n        /// <summary>\n        /// Opens the specified URL in the default browser.\n        /// </summary>\n        /// <param name=\"url\">The URL to open.</param>\n        public static void OpenBrowser(string url)\n        {\n            // Opens request in the browser.\n            try\n            {\n                System.Diagnostics.Process.Start(new ProcessStartInfo(url));\n            }\n            catch\n            {\n                Console.WriteLine(\"Hello\");\n                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n                {\n                    url = url.Replace(\"&\", \"^&\");\n                    System.Diagnostics.Process.Start(new ProcessStartInfo(\"cmd\", $\"/c start {url}\") { CreateNoWindow = true });\n                }\n                else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))\n                {\n                    System.Diagnostics.Process.Start(\"xdg-open\", url);\n                }\n                else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))\n                {\n                    System.Diagnostics.Process.Start(\"open\", url);\n                }\n                else\n                {\n                    throw;\n                }\n            }\n        }\n\n        /// <summary>\n        /// Converts a camel case or pascal case string into a human-readable format by inserting spaces between words.\n        /// </summary>\n        /// <param name=\"text\">The input string to be converted.</param>\n        /// <returns>The converted string with spaces between words.</returns>\n        public static string HumanifyText(string text)\n        {\n            var r = new Regex(@\"\n                (?<=[A-Z])(?=[A-Z][a-z]) |\n                 (?<=[^A-Z])(?=[A-Z]) |\n                 (?<=[A-Za-z])(?=[^A-Za-z])\", RegexOptions.IgnorePatternWhitespace);\n\n            return r.Replace(text, \" \");\n        }\n\n        /// <summary>\n        /// Converts the given HoursPassedStatus value to its corresponding string representation.\n        /// </summary>\n        /// <param name=\"status\">The HoursPassedStatus value to convert.</param>\n        /// <returns>The string representation of the HoursPassedStatus value.</returns>\n        public static string HoursPassedStatusString(HoursPassedStatus status) =>\n            status switch\n            {\n                HoursPassedStatus.Keep => \"Keep\",\n                HoursPassedStatus.White => \"White\",\n                HoursPassedStatus.Off => \"Off\",\n                _ => throw new ArgumentException(message: \"Invalid HoursPassedStatus Value\", paramName: nameof(status)),\n            };\n    \n        /// <summary>\n        /// Replaces the variables in the given body with the provided availability and activity.\n        /// </summary>\n        /// <param name=\"body\">The body in which to replace the variables.</param>\n        /// <param name=\"availability\">The availability to replace the {{availability}} variable.</param>\n        /// <param name=\"activity\">The activity to replace the {{activity}} variable.</param>\n        /// <returns>The body with the variables replaced.</returns>\n        public static string ReplaceVariables(string body, string? availability, string? activity)\n        {\n            if (body.Contains(\"{{availability}}\"))\n            {\n                body = body.Replace(\"{{availability}}\", availability ?? string.Empty);\n            }\n\n            if (body.Contains(\"{{activity}}\"))\n            {\n                body = body.Replace(\"{{activity}}\", activity ?? string.Empty);\n            }\n            return body;\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/CustomApiService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\n\nnamespace PresenceLight.Core\n{\n    public interface ICustomApiService\n    {\n        Task<string> SetColor(string availability, string? activity, CancellationToken cancellationToken = default);\n        void Initialize(AppState _appState);\n    }\n\n\n    public class CustomApiService : ICustomApiService\n    {\n        private MediatR.IMediator _mediator;\n        private string _currentAvailability = string.Empty;\n        private string _currentActivity = string.Empty;\n\n        HttpClient _client;\n\n        private readonly ILogger<CustomApiService> _logger;\n        private AppState _appState;\n\n        public CustomApiService(AppState appState, ILogger<CustomApiService> logger, MediatR.IMediator mediator)\n        {\n            _logger = logger;\n            _appState = appState;\n            _mediator = mediator;\n\n            _client = new HttpClient\n            {\n                Timeout = TimeSpan.FromSeconds(_appState.Config.LightSettings.CustomApi.CustomApiTimeout > 0 ?\n                                                   _appState.Config.LightSettings.CustomApi.CustomApiTimeout :\n                                                   20)\n            };\n        }\n\n        public void Initialize(AppState appState)\n        {\n            _appState = appState;\n        }\n\n        public async Task<string> SetColor(string availability, string? activity, CancellationToken cancellationToken = default)\n        {\n            string result = await SetAvailability(availability, cancellationToken);\n            result += await SetActivity(activity, cancellationToken);\n            return result;\n        }\n\n        private async Task<string> CallCustomApiForActivityChanged(object sender, string newActivity, CancellationToken cancellationToken)\n        {\n            string method = string.Empty;\n            string uri = string.Empty;\n            string body = string.Empty;\n            string result = string.Empty;\n\n            switch (newActivity)\n            {\n                case \"Available\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityAvailable.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityAvailable.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityAvailable.Body;\n                    break;\n                case \"Presenting\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityPresenting.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityPresenting.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityPresenting.Body;\n                    break;\n                case \"InACall\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityInACall.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityInACall.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityInACall.Body;\n                    break;\n                case \"InAConferenceCall\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityInAConferenceCall.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityInAConferenceCall.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityInAConferenceCall.Body;\n                    break;\n                case \"InAMeeting\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityInAMeeting.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityInAMeeting.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityInAMeeting.Body;\n                    break;\n                case \"Busy\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityBusy.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityBusy.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityBusy.Body;\n                    break;\n                case \"Away\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityAway.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityAway.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityAway.Body;\n                    break;\n                case \"BeRightBack\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityBeRightBack.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityBeRightBack.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityBeRightBack.Body;\n                    break;\n                case \"DoNotDisturb\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityDoNotDisturb.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityDoNotDisturb.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityDoNotDisturb.Body;\n                    break;\n                case \"Idle\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityIdle.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityIdle.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityIdle.Body;\n                    break;\n                case \"Offline\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityOffline.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityOffline.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityOffline.Body;\n                    break;\n                case \"Off\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiActivityOff.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiActivityOff.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiActivityOff.Body;\n                    break;\n                default:\n                    break;\n            }\n\n            return await PerformWebRequest(method, uri, body, result, cancellationToken);\n        }\n\n        private async Task<string> CallCustomApiForAvailabilityChanged(object sender, string newAvailability, CancellationToken cancellationToken)\n        {\n            string method = string.Empty;\n            string uri = string.Empty;\n            string body = string.Empty;\n            string result = string.Empty;\n\n            switch (newAvailability)\n            {\n                case \"Available\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiAvailable.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiAvailable.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiAvailable.Body;\n                    break;\n                case \"Busy\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiBusy.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiBusy.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiBusy.Body;\n                    break;\n                case \"BeRightBack\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiBeRightBack.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiBeRightBack.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiBeRightBack.Body;\n                    break;\n                case \"Away\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiAway.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiAway.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiAway.Body;\n                    break;\n                case \"DoNotDisturb\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiDoNotDisturb.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiDoNotDisturb.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiDoNotDisturb.Body;\n                    break;\n                case \"AvailableIdle\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiAvailableIdle.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiAvailableIdle.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiAvailableIdle.Body;\n                    break;\n                case \"Offline\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiOffline.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiOffline.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiOffline.Body;\n                    break;\n                case \"Off\":\n                    method = _appState.Config.LightSettings.CustomApi.CustomApiOff.Method;\n                    uri = _appState.Config.LightSettings.CustomApi.CustomApiOff.Uri;\n                    body = _appState.Config.LightSettings.CustomApi.CustomApiOff.Body;\n                    break;\n                default:\n                    break;\n            }\n\n            return await PerformWebRequest(method, uri, body, result, cancellationToken);\n        }\n\n        private async Task<string> SetAvailability(string availability, CancellationToken cancellationToken)\n        {\n            string result = string.Empty;\n            if (availability != _currentAvailability)\n            {\n                result = await CallCustomApiForAvailabilityChanged(this, availability, cancellationToken);\n                if (!cancellationToken.IsCancellationRequested)\n                {\n                    _currentAvailability = availability;\n                }\n                else\n                {\n                    // operation was cancelled\n                }\n\n            }\n            else\n            {\n                // availability did not change: don't spam call the api\n            }\n            return result;\n        }\n\n        private async Task<string> SetActivity(string activity, CancellationToken cancellationToken)\n        {\n            string result = string.Empty;\n            if (activity != _currentActivity)\n            {\n                result = await CallCustomApiForActivityChanged(this, activity, cancellationToken);\n                if (!cancellationToken.IsCancellationRequested)\n                {\n                    _currentActivity = activity;\n                }\n                else\n                {\n                    // operation was cancelled\n                }\n            }\n            else\n            {\n                // activity did not change: don't spam call the api\n            }\n            return result;\n        }\n\n        static Stack<string> _lastUriCalled = new Stack<string>(1);\n        private async Task<string> PerformWebRequest(string method, string uri, string body, string result, CancellationToken cancellationToken)\n        {\n            if (_lastUriCalled.Contains($\"{method}|{uri}\"))\n            {\n                _logger.LogDebug(\"No Change to State... NOT calling Api\");\n                return \"Skipped\";\n            }\n\n\n            using (Serilog.Context.LogContext.PushProperty(\"method\", method))\n            using (Serilog.Context.LogContext.PushProperty(\"uri\", uri))\n            using (Serilog.Context.LogContext.PushProperty(\"body\", body))\n            {\n                if (Helpers.AreStringsNotEmpty(new string[] { method, uri }))\n                {\n                    try\n                    {\n                        if (_appState.Config.LightSettings.CustomApi.IgnoreCertificateErrors)\n                        {\n                            var httpClientHandler = new HttpClientHandler();\n                            httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };\n                            _client = new HttpClient(httpClientHandler);\n                        }\n                        else\n                        {\n                            _client = new HttpClient();\n                        }\n\n                        _client.Timeout = TimeSpan.FromSeconds(_appState.Config.LightSettings.CustomApi.CustomApiTimeout > 0 ?\n                                                           _appState.Config.LightSettings.CustomApi.CustomApiTimeout :\n                                                           20);\n\n                        HttpResponseMessage response = new HttpResponseMessage();\n\n                        if (_appState.Config.LightSettings.CustomApi.UseBasicAuth)\n                        {\n                            var byteArray = Encoding.ASCII.GetBytes($\"{_appState.Config.LightSettings.CustomApi.BasicAuthUserName}:{_appState.Config.LightSettings.CustomApi.BasicAuthUserPassword}\");\n\n                            _client.DefaultRequestHeaders.Authorization = new\n                            AuthenticationHeaderValue(\"Basic\", Convert.ToBase64String(byteArray));\n                        }\n\n                        switch (method)\n                        {\n                            case \"GET\":\n                                response = await _client.GetAsync(uri, cancellationToken);\n                                break;\n                            case \"POST\":\n                                // check if body is empty\n                                if (string.IsNullOrEmpty(body))\n                                {\n                                    response = await _client.PostAsync(uri, null, cancellationToken);\n                                    break;\n                                }\n                                else\n                                {\n                                    // Replace any variables in the body\n                                    // The following variables are supported:\n                                    // {{availability}} - The current availability\n                                    // {{activity}} - The current activity\n                                    // Check if the body contains any variables using a regular expression and replace them\n                                    body = Helpers.ReplaceVariables(body, _appState.Presence.Availability, _appState.Presence.Activity);\n\n                                    var content = new StringContent(body, Encoding.UTF8, \"application/json\");\n                                    response = await _client.PostAsync(uri, content, cancellationToken);\n                                    break;\n                                }\n                                \n                        }\n\n\n                        string responseBody = await response.Content.ReadAsStringAsync(cancellationToken);\n                        result = $\"{(int)response.StatusCode} {response.StatusCode}: {responseBody}\";\n                        string message = $\"Sending {method} method to {uri} with body {body}\";\n\n                        _logger.LogInformation(message);\n                        _lastUriCalled.TryPop(out string res);\n                        _lastUriCalled.Push($\"{method}|{uri}|{body}\");\n\n                        using (Serilog.Context.LogContext.PushProperty(\"result\", result))\n                            _logger.LogDebug(message + \" Results\");\n                    }\n                    catch (Exception e)\n                    {\n                        _logger.LogError(e, \"Error Performing Web Request\");\n                        result = $\"Error: {e.Message}\";\n                    }\n                }\n\n                return result;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/Initialize/InitializeCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.Initialize\n{\n    public class InitializeCommand : IRequest\n    {\n        public AppState AppState { get;   set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/Initialize/InitializeHandler.cs",
    "content": "﻿using MediatR;\n\nusing PresenceLight.Core;\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing YeelightAPI.Models;\n\nnamespace PresenceLight.Core.Initialize\n{\n    internal class InitializeHandler : IRequestHandler<InitializeCommand>\n    {\n        readonly ICustomApiService _service;\n        public InitializeHandler(ICustomApiService service)\n        {\n            _service = service;\n        }\n\n        Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand command, CancellationToken cancellationToken)\n        {\n            _service.Initialize(command.AppState);\n            return Unit.Task;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/SetColor/SetColorCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.CustomApiServices\n{\n    public class SetColorCommand : IRequest<string>\n    {\n        public string Availability { get; set; }\n        public string Activity { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/SetColor/SetColorHandler.cs",
    "content": "﻿using MediatR;\nusing PresenceLight.Core;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.CustomApiServices\n{\n    internal class SetColorHandler : IRequestHandler<SetColorCommand, string>\n    {\n        readonly ICustomApiService _service;\n        public SetColorHandler(ICustomApiService service)\n        {\n            _service = service;\n        }\n\n        public async Task<string> Handle(SetColorCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.SetColor(command.Availability, command.Activity, cancellationToken);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/FindBridge/FindBridgeCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class FindBridgeCommand : IRequest<string>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/FindBridge/FindBridgeHandler.cs",
    "content": "﻿using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class FindBridgeHandler : IRequestHandler<FindBridgeCommand, string>\n    {\n        IHueService _service;\n        public FindBridgeHandler(IHueService hueService)\n        {\n            _service = hueService;\n        }\n\n        public async Task<string> Handle(FindBridgeCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.FindBridge();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/GetGroups/GetGroupsCommand.cs",
    "content": "﻿using HueApi.Models;\n\nusing MediatR;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class GetGroupsCommand : IRequest<IEnumerable<GroupedLight>>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/GetGroups/GetGroupsHandler.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing HueApi.Models;\n\nusing MediatR;\n\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class GetGroupsHandler : IRequestHandler<GetGroupsCommand, IEnumerable<GroupedLight>>\n    {\n        IHueService _service;\n        public GetGroupsHandler(IHueService hueService)\n        {\n            _service = hueService;\n        }\n\n        public async Task<IEnumerable<GroupedLight>> Handle(GetGroupsCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.GetGroups();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/GetLights/GetLightsCommand.cs",
    "content": "﻿using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class GetLightsCommand : IRequest<IEnumerable<Light>>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/GetLights/GetLightsHandler.cs",
    "content": "﻿using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class GetLightsHandler : IRequestHandler<GetLightsCommand, IEnumerable<Light>>\n    {\n        IHueService _service;\n        public GetLightsHandler(IHueService hueService)\n        {\n            _service = hueService;\n        }\n\n        public async Task<IEnumerable<Light>> Handle(GetLightsCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.GetLights();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/HueService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nusing HueApi;\nusing HueApi.BridgeLocator;\nusing HueApi.ColorConverters.Original.Extensions;\nusing HueApi.Models;\nusing HueApi.Models.Requests;\n\nusing Microsoft.Extensions.Logging;\n\n\nnamespace PresenceLight.Core\n{\n    public interface IHueService\n    {\n        Task SetColor(string availability, string activity, string lightId);\n        Task<string> RegisterBridge();\n        Task<IEnumerable<Light>> GetLights();\n\n        Task<IEnumerable<GroupedLight>> GetGroups();\n        Task<string> FindBridge();\n        void Initialize(AppState appState);\n    }\n    public class HueService : IHueService\n    {\n        private AppState _appState;\n        private LocalHueApi _client;\n        private readonly ILogger<HueService> _logger;\n\n        public HueService(AppState appState, ILogger<HueService> logger)\n        {\n            _logger = logger;\n            _appState = appState;\n        }\n\n        public void Initialize(AppState appState)\n        {\n            _appState = appState;\n        }\n\n        public async Task SetColor(string availability, string activity, string lightId)\n        {\n            if (_appState.HueLights == null || _appState.HueLights.Count() == 0)\n            {\n                if (lightId.Contains(\"group_id:\"))\n                {\n                    _appState.SetHueLights(await GetGroups());\n                }\n                else\n                {\n                    _appState.SetHueLights(await GetLights());\n                }\n            }\n\n            if (string.IsNullOrEmpty(lightId))\n            {\n                _logger.LogInformation(\"Selected Hue Light Not Specified\");\n                return;\n            }\n\n            try\n            {\n                _client = new LocalHueApi(_appState.Config.LightSettings.Hue.HueIpAddress, _appState.Config.LightSettings.Hue.HueApiKey);\n\n                var o = await Handle(_appState.Config.LightSettings.Hue.UseActivityStatus ? activity : availability, lightId);\n\n                if (o.returnFunc)\n                {\n                    return;\n                }\n\n                var color = o.color.Replace(\"#\", \"\");\n                var command = o.command;\n                var message = \"\";\n                switch (color.Length)\n                {\n                    case var length when color.Length == 6:\n                        // Do Nothing\n                        break;\n                    case var length when color.Length > 6:\n                        // Get last 6 characters\n                        color = color.Substring(0, 6);\n                        break;\n                    default:\n                        throw new ArgumentException(\"Supplied Color had an issue\");\n                }\n\n                var rgbColor = new HueApi.ColorConverters.RGBColor(color);\n                // Set the color using extension method\n                command.SetColor(rgbColor);\n\n\n\n                if (availability == \"Off\")\n                {\n                    command.TurnOff();\n\n                    if (lightId.Contains(\"group_id:\"))\n                    {\n                        var groupCommand = new UpdateGroupedLight();\n                        groupCommand.TurnOff();\n                        await _client.UpdateGroupedLightAsync(Guid.Parse(lightId.Replace(\"group_id:\", \"\")), groupCommand);\n                    }\n                    else\n                    {\n                        await _client.UpdateLightAsync(Guid.Parse(lightId.Replace(\"id:\", \"\")), command);\n                    }\n\n                    message = $\"Turning Hue Light {lightId} Off\";\n                    _logger.LogInformation(message);\n                    return;\n                }\n\n                if (_appState.Config.LightSettings.UseDefaultBrightness)\n                {\n                    if (_appState.Config.LightSettings.DefaultBrightness == 0)\n                    {\n                        command.TurnOff();\n                    }\n                    else\n                    {\n                        command.TurnOn();\n                        command.Dimming = new Dimming { Brightness = Convert.ToDouble(_appState.Config.LightSettings.DefaultBrightness) };\n                        command.Dynamics = new Dynamics { Duration = 0 };\n                    }\n                }\n                else\n                {\n                    if (_appState.Config.LightSettings.Hue.Brightness == 0)\n                    {\n                        command.TurnOff();\n                    }\n                    else\n                    {\n                        command.TurnOn();\n                        command.Dimming = new Dimming { Brightness = Convert.ToDouble(_appState.Config.LightSettings.Hue.Brightness) };\n                        command.Dynamics = new Dynamics { Duration = 0 };\n                    }\n                }\n\n                if (lightId.Contains(\"group_id:\"))\n                {\n                    var newLightId = ((GroupedLight)_appState.HueLights.First(a => ((GroupedLight)a).IdV1 == lightId.Replace(\"group_id:\", \"\"))).Id;\n\n\n                    var groupCommand = new UpdateGroupedLight();\n                    groupCommand.Color = command.Color;\n                    groupCommand.On = command.On;\n                    groupCommand.Dimming = command.Dimming;\n                    groupCommand.Dynamics = command.Dynamics;\n                    await _client.GroupedLight.UpdateAsync(newLightId, groupCommand);\n                }\n                else\n                {\n                    var newLightId = ((Light)_appState.HueLights.First(a => ((Light)a).IdV1 == lightId.Replace(\"id:\", \"\"))).Id;\n                    await _client.Light.UpdateAsync(newLightId, command);\n                }\n\n                message = $\"Setting Hue Light {lightId} to {color}\";\n                _logger.LogInformation(message);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Setting Color\");\n                throw;\n            }\n        }\n\n        //Need to wire up a way to do this without user intervention\n        public async Task<string> RegisterBridge()\n        {\n            if (string.IsNullOrEmpty(_appState.Config.LightSettings.Hue.HueApiKey))\n            {\n                try\n                {\n                    _logger.LogInformation(\"Registering with Hue Bridge - Please press the button on your bridge\");\n                    var result = await LocalHueApi.RegisterAsync(_appState.Config.LightSettings.Hue.HueIpAddress, \"PresenceLight\", Environment.MachineName, true);\n                    return result.Username; // RegisterAsync returns RegisterEntertainmentResult with Username property\n                }\n                catch (Exception e)\n                {\n                    _logger.LogError(e, \"Error Occurred Registering Bridge\");\n                    return String.Empty;\n                }\n            }\n            return _appState.Config.LightSettings.Hue.HueApiKey;\n        }\n\n        public async Task<string> FindBridge()\n        {\n            try\n            {\n                HttpBridgeLocator locator = new HttpBridgeLocator();\n                var bridges = await locator.LocateBridgesAsync(TimeSpan.FromSeconds(5));\n                if (bridges.Any())\n                {\n                    return bridges.FirstOrDefault().IpAddress;\n                }\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Finding Bridge\");\n                return String.Empty;\n            }\n            return String.Empty;\n        }\n\n        public async Task<IEnumerable<Light>> GetLights()\n        {\n            try\n            {\n                if (_client == null)\n                {\n                    _client = new LocalHueApi(_appState.Config.LightSettings.Hue.HueIpAddress, _appState.Config.LightSettings.Hue.HueApiKey);\n                }\n                var lightsResponse = await _client.Light.GetAllAsync();\n                return lightsResponse.Data;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, message: \"Error Occurred Getting Lights\");\n                throw;\n            }\n        }\n\n        public async Task<IEnumerable<GroupedLight>> GetGroups()\n        {\n            try\n            {\n                if (_client == null)\n                {\n                    _client = new LocalHueApi(_appState.Config.LightSettings.Hue.HueIpAddress, _appState.Config.LightSettings.Hue.HueApiKey);\n                }\n                var groupsResponse = await _client.GroupedLight.GetAllAsync();\n                return groupsResponse.Data;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Getting Groups\");\n                throw;\n            }\n        }\n\n        private async Task<(string color, UpdateLight command, bool returnFunc)> Handle(string presence, string lightId)\n        {\n            var props = _appState.Config.LightSettings.Hue.Statuses.GetType().GetProperties().ToList();\n\n            if (_appState.Config.LightSettings.Hue.UseActivityStatus)\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"activity\")).ToList();\n            }\n            else\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"availability\")).ToList();\n            }\n\n            string color = \"\";\n            string message;\n            var command = new UpdateLight();\n\n            if (presence.Contains('#'))\n            {\n                // provided presence is actually a custom color\n                color = presence;\n                command.TurnOn();\n                return (color, command, false);\n            }\n\n            foreach (var prop in props)\n            {\n                if (presence == prop.Name.Replace(\"Status\", \"\").Replace(\"Availability\", \"\").Replace(\"Activity\", \"\"))\n                {\n                    var value = (AvailabilityStatus)prop.GetValue(_appState.Config.LightSettings.Hue.Statuses);\n\n                    if (!value.Disabled)\n                    {\n                        command.TurnOn();\n                        color = value.Color;\n                        return (color, command, false);\n                    }\n                    else\n                    {\n                        command.TurnOff();\n\n                        if (lightId.Contains(\"group_id:\"))\n                        {\n                            var groupCommand = new UpdateGroupedLight();\n                            groupCommand.TurnOff();\n                            await _client.UpdateGroupedLightAsync(Guid.Parse(lightId.Replace(\"group_id:\", \"\")), groupCommand);\n                        }\n                        else\n                        {\n                            await _client.UpdateLightAsync(Guid.Parse(lightId.Replace(\"id:\", \"\")), command);\n                        }\n                        message = $\"Turning Hue Light {lightId} Off\";\n                        _logger.LogInformation(message);\n                        return (color, command, true);\n                    }\n                }\n            }\n            return (color, command, false);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/Initialize/InitializeCommand.cs",
    "content": "﻿using MediatR;\n\nusing System;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class InitializeCommand : IRequest\n    {\n        public AppState AppState { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/Initialize/InitializeHandler.cs",
    "content": "﻿using MediatR;\n\nusing PresenceLight.Core;\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueServices\n{\n    internal class InitializeHandler : IRequestHandler<InitializeCommand>\n    {\n        IHueService _service;\n        public InitializeHandler(IHueService hueService)\n        {\n            _service = hueService;\n        }\n\n\n        async Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand command, CancellationToken cancellationToken)\n        {\n            _service.Initialize(command.AppState);\n            await Task.CompletedTask;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/RegisterBridge/RegisterBridgeCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class RegisterBridgeCommand : IRequest<string>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/RegisterBridge/RegisterBridgeHandler.cs",
    "content": "﻿using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class RegisterBridgeHandler : IRequestHandler<RegisterBridgeCommand, string>\n    {\n        IHueService _service;\n        public RegisterBridgeHandler(IHueService hueService)\n        {\n            _service = hueService;\n        }\n\n        public async Task<string> Handle(RegisterBridgeCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.RegisterBridge();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/SetColor/SetColorCommand.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class SetColorCommand : IRequest<Unit>\n    {\n        public string Availability { get; set; }\n        public string Activity { get; set; }\n        public string LightID { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/SetColor/SetColorHandler.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueServices.HueService\n{\n    public class SetColorHandler : IRequestHandler<SetColorCommand, Unit>\n    {\n        IHueService _service;\n        public SetColorHandler(IHueService hueService)\n        {\n            _service = hueService;\n        }\n\n        public  async Task<Unit> Handle(SetColorCommand command, CancellationToken cancellationToken)\n        {\n            await _service.SetColor(command.Availability, command.Activity, command.LightID);\n            return default;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/GetAllGroups/GetAllGroupsCommand.cs",
    "content": "﻿using LifxCloud.NET.Models;\n\nusing MediatR;\n\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    public class GetAllGroupsCommand : IRequest<List<Group>>\n    {\n        public string ApiKey { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/GetAllGroups/GetAllGroupsHandler.cs",
    "content": "﻿using LifxCloud.NET;\nusing LifxCloud.NET.Models;\nusing MediatR;\nusing Microsoft.Extensions.Logging;\nusing PresenceLight.Core;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    internal class GetAllGroupsHandler : IRequestHandler<GetAllGroupsCommand, List<Group>>\n    {\n        LIFXService _service;\n        public GetAllGroupsHandler(LIFXService service)\n        {\n            _service = service;\n        }\n\n        public async Task<List<Group>> Handle(GetAllGroupsCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.GetAllGroups(command.ApiKey);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/GetAllLights/GetAllLightsCommand.cs",
    "content": "﻿using LifxCloud.NET.Models;\n\nusing MediatR;\n\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    public class GetAllLightsCommand : IRequest<List<Light>>\n    {\n        public string ApiKey { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/GetAllLights/GetAllLightsHandler.cs",
    "content": "﻿using LifxCloud.NET;\nusing LifxCloud.NET.Models;\nusing MediatR;\nusing Microsoft.Extensions.Logging;\nusing PresenceLight.Core;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    internal class GetAllLightsHandler : IRequestHandler<GetAllLightsCommand, List<Light>>\n    {\n        LIFXService _service;\n        public GetAllLightsHandler(LIFXService service)\n        {\n            _service = service;\n        }\n\n        public async Task<List<Light>> Handle(GetAllLightsCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.GetAllLights(command.ApiKey);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/Initialize/InitializeCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    public class InitializeCommand : IRequest\n    {\n        public AppState AppState { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/Initialize/InitializeHandler.cs",
    "content": "﻿using MediatR;\n\nusing PresenceLight.Core;\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing YeelightAPI.Models;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    internal class InitializeHandler : IRequestHandler<InitializeCommand>\n    {\n        LIFXService _service;\n        public InitializeHandler(LIFXService service)\n        {\n            _service = service;\n        }\n\n        async Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand command, CancellationToken cancellationToken)\n        {\n            _service.Initialize(command.AppState);\n            await Task.CompletedTask;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/LIFXOAuthHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Net.Http;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Web;\n\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.WebUtilities;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Primitives;\n\nusing Newtonsoft.Json;\n\n\nnamespace PresenceLight.Core\n{\n    public class LIFXOAuthHelper\n    {\n        private const string LIFXAuthority = \"https://cloud.lifx.com/oauth\";\n        private readonly string _lIFXTokenEndpoint = $\"{LIFXAuthority}/token\";\n        private readonly string _lIFXAuthorizationEndpoint = $\"{LIFXAuthority}/authorize\";\n        private readonly AppState _appState;\n\n        public LIFXOAuthHelper(AppState appState)\n        {\n            _appState = appState;\n\n        }\n\n        public async Task<string> InitiateTokenRetrieval()\n        {\n\n            var builder = WebApplication.CreateBuilder();\n            builder.Logging.ClearProviders();\n\n            var app = builder.Build();\n\n            var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);\n\n            app.Run(async ctx =>\n            {\n                Task WriteResponse(HttpContext ctx)\n                {\n                    ctx.Response.StatusCode = 200;\n                    ctx.Response.ContentType = \"text/html\";\n                    return ctx.Response.WriteAsync(\"<html><head><meta http-equiv='refresh' content='10;url=https://lifx.com'></head><body>Please return to the app.</body></html>\", Encoding.UTF8);\n                }\n\n                switch (ctx.Request.Method)\n                {\n                    case \"GET\":\n                        await WriteResponse(ctx);\n\n                        tcs.TrySetResult(ctx.Request.QueryString.Value);\n                        break;\n\n                    case \"POST\" when !ctx.Request.HasFormContentType:\n                        ctx.Response.StatusCode = 415;\n                        break;\n\n                    case \"POST\":\n                        {\n                            using var sr = new StreamReader(ctx.Request.Body, Encoding.UTF8);\n                            var body = await sr.ReadToEndAsync();\n\n                            await WriteResponse(ctx);\n\n                            tcs.TrySetResult(body);\n                            break;\n                        }\n\n                    default:\n                        ctx.Response.StatusCode = 405;\n                        break;\n                }\n            });\n\n            var browserPort = 17236;\n\n            app.Urls.Add($\"http://localhost:{browserPort}/\");\n\n            app.Start();\n\n            var timeout = TimeSpan.FromMinutes(5);\n\n            string redirectUri = string.Format($\"http://localhost:{browserPort}/\");\n\n            string state = RandomDataBase64Url(32);\n\n            string authorizationRequest = string.Format(\"{0}?response_type=code&scope=remote_control:all&client_id={1}&state={2}&redirect_uri={3}\",\n                _lIFXAuthorizationEndpoint,\n                _appState.Config.LightSettings.LIFX.LIFXClientId,\n                state,\n              HttpUtility.UrlEncode(redirectUri)\n                );\n\n            Helpers.OpenBrowser(authorizationRequest);\n\n            var qs = await tcs.Task.WaitAsync(timeout);\n\n            var qsDict = QueryHelpers.ParseQuery(qs.Replace(\"?\", \"\"));\n            StringValues code;\n            qsDict.TryGetValue(\"code\", out code);\n            await app.DisposeAsync();\n\n            var formContent = new FormUrlEncodedContent(new[]\n            {\n                new KeyValuePair<string, string>(\"code\", code.ToString()),\n                new KeyValuePair<string, string>(\"client_id\", _appState.Config.LightSettings.LIFX.LIFXClientId),\n                new KeyValuePair<string, string>(\"client_secret\",  _appState.Config.LightSettings.LIFX.LIFXClientSecret),\n                new KeyValuePair<string, string>(\"grant_type\", \"authorization_code\")\n            });\n\n\n            // sends the request\n            HttpClient _client = new HttpClient();\n            _client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(\"application/json\"));\n            var response = await _client.PostAsync(_lIFXTokenEndpoint, formContent);\n\n            string responseText = await response.Content.ReadAsStringAsync();\n\n            Dictionary<string, string> tokenEndpointDecoded = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseText);\n\n            string _accessToken = tokenEndpointDecoded[\"access_token\"];\n\n\n            return _accessToken;\n        }\n\n\n        /// <summary>\n        /// Returns URI-safe data with a given input length.\n        /// </summary>\n        /// <param name=\"length\">Input length (nb. output will be longer)</param>\n        /// <returns></returns>\n        private static string RandomDataBase64Url(uint length)\n        {\n            var rng = RandomNumberGenerator.Create();\n            byte[] bytes = new byte[length];\n            rng.GetBytes(bytes);\n            return Base64UrlEncodeNoPadding(bytes);\n        }\n\n        /// <summary>\n        /// Base64url no-padding encodes the given input buffer.\n        /// </summary>\n        /// <param name=\"buffer\"></param>\n        /// <returns></returns>\n        private static string Base64UrlEncodeNoPadding(byte[] buffer)\n        {\n            string base64 = Convert.ToBase64String(buffer);\n\n            // Converts base64 to base64url.\n            base64 = base64.Replace(\"+\", \"-\", StringComparison.OrdinalIgnoreCase);\n            base64 = base64.Replace(\"/\", \"_\", StringComparison.OrdinalIgnoreCase);\n            // Strips padding.\n            base64 = base64.Replace(\"=\", \"\", StringComparison.OrdinalIgnoreCase);\n\n            return base64;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/LifxService.cs",
    "content": "﻿using System;\nusing LifxCloud.NET;\nusing LifxCloud.NET.Models;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Logging;\nusing System.Linq;\n\nnamespace PresenceLight.Core\n{\n    public class LIFXService\n    {\n        private AppState _appState;\n        private LifxCloudClient _client;\n        private readonly ILogger<LIFXService> _logger;\n        MediatR.IMediator _mediator;\n\n        public LIFXService(AppState appState, MediatR.IMediator mediator, ILogger<LIFXService> logger)\n        {\n            _appState = appState;\n            _logger = logger;\n            _mediator = mediator;\n        }\n\n        public void Initialize(AppState appState)\n        {\n            _appState = appState;\n        }\n\n        public async Task<List<Light>> GetAllLights(string apiKey = null)\n        {\n            try\n            {\n                if (!string.IsNullOrEmpty(apiKey))\n                {\n                    _appState.Config.LightSettings.LIFX.LIFXApiKey = apiKey;\n                }\n\n                if (!_appState.Config.LightSettings.LIFX.IsEnabled || string.IsNullOrEmpty(_appState.Config.LightSettings.LIFX.LIFXApiKey))\n                {\n                    return new List<Light>();\n                }\n\n                _client = await LifxCloudClient.CreateAsync(_appState.Config.LightSettings.LIFX.LIFXApiKey);\n                return await _client.ListLights(Selector.All);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Getting Lights\");\n                throw;\n            }\n        }\n\n        public async Task<List<Group>> GetAllGroups(string apiKey = null)\n        {\n            try\n            {\n                if (!string.IsNullOrEmpty(apiKey))\n                {\n                    _appState.Config.LightSettings.LIFX.LIFXApiKey = apiKey;\n                }\n                if (!_appState.Config.LightSettings.LIFX.IsEnabled || string.IsNullOrEmpty(_appState.Config.LightSettings.LIFX.LIFXApiKey))\n                {\n                    return new List<Group>();\n                }\n\n                _client = await LifxCloudClient.CreateAsync(_appState.Config.LightSettings.LIFX.LIFXApiKey);\n                return await _client.ListGroups(Selector.All);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Getting Groups\");\n                throw;\n            }\n        }\n\n        public async Task SetColor(string availability, string activity, string lightId, string apiKey = null)\n        {\n            if (string.IsNullOrEmpty(lightId))\n            {\n                _logger.LogInformation(\"Selected LIFX Light Not Specified\");\n                return;\n            }\n\n            Selector selector = null;\n\n            if (!lightId.Contains(\"group\"))\n            {\n                selector = new Selector.LightId(lightId.Replace(\"id:\", \"\"));\n            }\n            else\n            {\n                selector = new Selector.GroupId(lightId.Replace(\"group_id:\", \"\"));\n            }\n\n            if (!string.IsNullOrEmpty(apiKey))\n            {\n                _appState.Config.LightSettings.LIFX.LIFXApiKey = apiKey;\n            }\n            if (!_appState.Config.LightSettings.LIFX.IsEnabled || string.IsNullOrEmpty(_appState.Config.LightSettings.LIFX.LIFXApiKey))\n            {\n                return;\n            }\n\n            try\n            {\n                _client = await LifxCloudClient.CreateAsync(_appState.Config.LightSettings.LIFX.LIFXApiKey);\n\n                var o = await Handle(_appState.Config.LightSettings.LIFX.UseActivityStatus ? activity : availability, lightId);\n\n                if (o.returnFunc)\n                {\n                    return;\n                }\n\n                var color = o.color.Replace(\"#\", \"\");\n                var command = o.command;\n                var message = \"\";\n                switch (color.Length)\n                {\n\n                    case var length when color.Length == 6:\n                        // Do Nothing\n                        break;\n                    case var length when color.Length > 6:\n                        // Get last 6 characters\n                        color = color.Substring(0,6);\n                        break;\n                    default:\n                        throw new ArgumentException(\"Supplied Color had an issue\");\n                }\n\n                if (availability == \"Off\")\n                {\n                    _logger.LogInformation($\"Turning LIFX Light {lightId} Off - LIFXService:SetColor\");\n                    command.Power = PowerState.Off;\n                    var result = await _client.SetState(selector, command);\n                    return;\n                }\n\n                if (_appState.Config.LightSettings.UseDefaultBrightness)\n                {\n                    if (_appState.Config.LightSettings.DefaultBrightness == 0)\n                    {\n                        command.Power = PowerState.Off;\n                    }\n                    else\n                    {\n                        command.Power = PowerState.On;\n                        command.Brightness = Convert.ToDouble(_appState.Config.LightSettings.DefaultBrightness) / 100;\n                        command.Duration = 0;\n                    }\n                }\n                else\n                {\n                    if (_appState.Config.LightSettings.LIFX.Brightness == 0)\n                    {\n                        command.Power = PowerState.Off;\n                    }\n                    else\n                    {\n                        command.Power = PowerState.On;\n                        command.Brightness = Convert.ToDouble(_appState.Config.LightSettings.DefaultBrightness) / 100;\n                        command.Duration = 0;\n                    }\n                }\n                command.Color = color;\n                await _client.SetState(selector, command);\n\n                message = $\"Setting LIFX Light {lightId} to {color}\";\n                _logger.LogInformation(message);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Setting Color\");\n                throw;\n            }\n        }\n\n        private async Task<(string color, SetStateRequest command, bool returnFunc)> Handle(string presence, string lightId)\n        {\n            var props = _appState.Config.LightSettings.LIFX.Statuses.GetType().GetProperties().ToList();\n\n            if (_appState.Config.LightSettings.LIFX.UseActivityStatus)\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"activity\")).ToList();\n            }\n            else\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"availability\")).ToList();\n            }\n\n            string color = \"\";\n            string message;\n            var command = new SetStateRequest();\n\n            if (presence.Contains(\"#\"))\n            {\n                // provided presence is actually a custom color\n                color = presence;\n                command.Power = PowerState.On;\n                return (color, command, false);\n            }\n\n            foreach (var prop in props)\n            {\n                if (presence == prop.Name.Replace(\"Status\", \"\").Replace(\"Availability\", \"\").Replace(\"Activity\", \"\"))\n                {\n                    var value = (AvailabilityStatus)prop.GetValue(_appState.Config.LightSettings.LIFX.Statuses);\n\n                    if (!value.Disabled)\n                    {\n                        command.Power = PowerState.On;\n                        color = value.Color;\n                        return (color, command, false);\n                    }\n                    else\n                    {\n                        command.Power = PowerState.Off;\n\n\n                        Selector selector = null;\n\n                        if (!lightId.Contains(\"group\"))\n                        {\n                            selector = new Selector.LightId(lightId.Replace(\"id:\", \"\"));\n                        }\n                        else\n                        {\n                            selector = new Selector.GroupId(lightId.Replace(\"group_id:\", \"\"));\n                        }\n\n                        await _client.SetState(selector, command);\n\n                        message = $\"Turning LIFX Light {lightId} Off\";\n                        _logger.LogInformation(message);\n                        return (color, command, true);\n                    }\n                }\n            }\n            return (color, command, false);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/SetColor/SetColorCommand.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    public class SetColorCommand : IRequest<Unit>\n    {\n        public string Availability { get; set; }\n        public string Activity { get; set; }\n        public string? LightId { get; set; }\n        public string ApiKey { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/SetColor/SetColorHandler.cs",
    "content": "﻿using LifxCloud.NET;\n\nusing MediatR;\n\nusing Microsoft.Extensions.Logging;\n\nusing PresenceLight.Core;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    internal class SetColorHandler : IRequestHandler<SetColorCommand, Unit>\n    {\n        LIFXService _service;\n        public SetColorHandler(LIFXService service)\n        {\n            _service = service;\n        }\n\n        public async Task<Unit> Handle(SetColorCommand command, CancellationToken cancellationToken)\n        {\n            await _service.SetColor(command.Availability, command.Activity, command.LightId, command.ApiKey);\n            return default;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/GetSerialHosts/GetSerialHostsCommand.cs",
    "content": "using MediatR;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n    public class GetPortCommand : IRequest<IEnumerable<string>>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/GetSerialHosts/GetSerialHostsHandler.cs",
    "content": "using MediatR;\n\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n\n    internal class GetAvailablePortsHandler : IRequestHandler<GetPortCommand, IEnumerable<string>>\n    {\n        ILocalSerialHostService _service;\n\n        public GetAvailablePortsHandler(ILocalSerialHostService service)\n        {\n            _service = service;\n        }\n\n        public async Task<IEnumerable<string>> Handle(GetPortCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.GetPorts();\n        }\n    }\n}"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/Initialize/InitializeCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n    public class InitializeCommand : IRequest\n    {\n        public AppState AppState { get;   set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/Initialize/InitializeHandler.cs",
    "content": "﻿using MediatR;\n\nusing PresenceLight.Core;\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing YeelightAPI.Models;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n    internal class InitializeHandler : IRequestHandler<InitializeCommand>\n    {\n        readonly ILocalSerialHostService _service;\n        public InitializeHandler(ILocalSerialHostService service)\n        {\n            _service = service;\n            \n        }\n\n        Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand command, CancellationToken cancellationToken)\n        {\n            _service.Initialize(command.AppState);\n            return Unit.Task;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/LocalSerialHost.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO.Ports;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\n\nnamespace PresenceLight.Core\n{\n    public interface ILocalSerialHostService\n    {\n        Task<string> SetColor(string availability, string? activity, CancellationToken cancellationToken = default);\n        Task<IEnumerable<string>> GetPorts();\n        void Initialize(AppState _appState);\n    }\n\n\n    public class LocalSerialHostService : ILocalSerialHostService\n    {\n        private MediatR.IMediator _mediator;\n        private string _currentAvailability = string.Empty;\n        private string _currentActivity = string.Empty;\n        private SerialPort _port = null;\n        private string _lineEnding = \"\";\n\n        private readonly ILogger<LocalSerialHostService> _logger;\n        private AppState _appState;\n        private readonly object serialWriteLock = new object();\n\n        public LocalSerialHostService(AppState appState, ILogger<LocalSerialHostService> logger, MediatR.IMediator mediator)\n        {\n            _logger = logger;\n            _appState = appState;\n            _mediator = mediator;\n\n            switch (appState.Config.LightSettings.LocalSerialHost.LocalSerialHostMainSetup.LineEnding)\n            {\n                case \"CR\" :\n                    _lineEnding = \"\\r\";\n                    break;\n                case \"LF\" :\n                    _lineEnding = \"\\n\";\n                    break;\n                case \"CRLF\" :\n                    _lineEnding = \"\\r\\n\";\n                    break;\n                default :\n                    _logger.LogDebug(\"Line endings not set or empty string\");\n                    _lineEnding = \"\";\n                    break;\n            }\n\n            if (!string.IsNullOrEmpty(appState.Config.LightSettings.LocalSerialHost.LocalSerialHostMainSetup.Port))\n            {\n                SetupSerialPort(appState.Config.LightSettings.LocalSerialHost.LocalSerialHostMainSetup.Port);\n            }\n        }\n\n        ~LocalSerialHostService()\n        {\n            if (_port != null && _port.IsOpen)\n            {\n                _port.Close();\n            }\n        }\n\n        public void Initialize(AppState appState)\n        {\n            _appState = appState;\n\n            if (_port != null && _port.IsOpen)\n            {\n                _port.Close();\n                _port  = null;\n            }\n\n            switch (appState.Config.LightSettings.LocalSerialHost.LocalSerialHostMainSetup.LineEnding)\n            {\n                case \"CR\" :\n                    _lineEnding = \"\\r\";\n                    break;\n                case \"LF\" :\n                    _lineEnding = \"\\n\";\n                    break;\n                case \"CRLF\" :\n                    _lineEnding = \"\\r\\n\";\n                    break;\n                default :\n                    _logger.LogDebug(\"Line endings not set or empty string\");\n                    _lineEnding = \"\";\n                    break;\n            }\n\n            if (!string.IsNullOrEmpty(appState.Config.LightSettings.LocalSerialHost.LocalSerialHostMainSetup.Port))\n            {\n                SetupSerialPort(appState.Config.LightSettings.LocalSerialHost.LocalSerialHostMainSetup.Port);\n            }\n        }\n\n        public async Task<string> SetColor(string availability, string? activity, CancellationToken cancellationToken = default)\n        {\n            string result = await SetAvailability(availability, cancellationToken);\n            result += await SetActivity(activity, cancellationToken);\n            return result;\n        }\n\n        private async Task<string> CallLocalSerialHostForActivityChanged(object sender, string newActivity, CancellationToken cancellationToken)\n        {\n            string message = string.Empty;\n            string result = string.Empty;\n\n            switch (newActivity)\n            {\n                case \"Available\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityAvailable;\n                    break;\n                case \"Presenting\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityPresenting;\n                    break;\n                case \"InACall\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityInACall;\n                    break;\n                case \"InAConferenceCall\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityInAConferenceCall;\n                    break;\n                case \"InAMeeting\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityInAMeeting;\n                    break;\n                case \"Busy\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityBusy;\n                    break;\n                case \"Away\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityAway;\n                    break;\n                case \"BeRightBack\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityBeRightBack;\n                    break;\n                case \"DoNotDisturb\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityDoNotDisturb;\n                    break;\n                case \"Idle\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityIdle;\n                    break;\n                case \"Offline\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityOffline;\n                    break;\n                case \"Off\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostActivityOff;\n                    break;\n                default:\n                    break;\n            }\n\n            return await PerformSerialMessage(message, result, cancellationToken);\n        }\n\n        private async Task<string> CallLocalSerialHostForAvailabilityChanged(object sender, string newAvailability, CancellationToken cancellationToken)\n        {\n            string message = string.Empty;\n            string result = string.Empty;\n\n            switch (newAvailability)\n            {\n                case \"Available\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostAvailable;\n                    break;\n                case \"Busy\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostBusy;\n                    break;\n                case \"BeRightBack\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostBeRightBack;\n                    break;\n                case \"Away\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostAway;\n                    break;\n                case \"DoNotDisturb\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostDoNotDisturb;\n                    break;\n                case \"AvailableIdle\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostAvailableIdle;\n                    break;\n                case \"Offline\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostOffline;\n                    break;\n                case \"Off\":\n                    message = _appState.Config.LightSettings.LocalSerialHost.LocalSerialHostOff;\n                    break;\n                default:\n                    break;\n            }\n\n            return await PerformSerialMessage(message, result, cancellationToken);\n        }\n\n        private async Task<string> SetAvailability(string availability, CancellationToken cancellationToken)\n        {\n            string result = string.Empty;\n            if (availability != _currentAvailability)\n            {\n                result = await CallLocalSerialHostForAvailabilityChanged(this, availability, cancellationToken);\n                if (!cancellationToken.IsCancellationRequested)\n                {\n                    _currentAvailability = availability;\n                }\n                else\n                {\n                    // operation was cancelled\n                }\n\n            }\n            else\n            {\n                // availability did not change: don't spam call the api\n            }\n            return result;\n        }\n\n        private async Task<string> SetActivity(string activity, CancellationToken cancellationToken)\n        {\n            string result = string.Empty;\n            if (activity != _currentActivity)\n            {\n                result = await CallLocalSerialHostForActivityChanged(this, activity, cancellationToken);\n                if (!cancellationToken.IsCancellationRequested)\n                {\n                    _currentActivity = activity;\n                }\n                else\n                {\n                    // operation was cancelled\n                }\n            }\n            else\n            {\n                // activity did not change: don't spam call the api\n            }\n            return result;\n        }\n\n        static Stack<string> _lastLineEndingCalled = new Stack<string>(1);\n        private async Task<string> PerformSerialMessage(string serialMessage, string result, CancellationToken cancellationToken)\n        {\n            if (_lastLineEndingCalled.Contains($\"{serialMessage}\"))\n            {\n                _logger.LogDebug(\"No Change to State... NOT calling Api\");\n                return \"Skipped\";\n            }\n\n            using (Serilog.Context.LogContext.PushProperty(\"message\", serialMessage))\n            {\n                if (!string.IsNullOrEmpty(serialMessage))\n                {\n                    try\n                    {\n                        if (_port == null || !_port.IsOpen)\n                        {\n                            _logger.LogWarning(\"Serial Port not setup in PerformSerialMessage. Attempting to initialize\");\n                            SetupSerialPort(_appState.Config.LightSettings.LocalSerialHost.LocalSerialHostMainSetup.Port);\n                        }\n\n                        Task<string> writeTask = Task<string>.Run(() => \n                        {\n                            string writeResult = \"\";\n                            try\n                            {\n                                lock (serialWriteLock)\n                                {\n                                    _port.Write(serialMessage + _lineEnding);\n                                    writeResult = _port.ReadLine();\n                                }\n                            }\n                            catch (Exception e)\n                            {\n                                _logger.LogError(e, \"Error Performing Serial Write\");\n                                writeResult = $\"Error: {e.Message}\";\n                            }\n\n                            return writeResult.Trim();\n                        });\n                        \n                        string message = $\"Sending {serialMessage} to {_port.PortName}\";\n                        result = await Task.WhenAny(writeTask).Result;\n\n                        _logger.LogInformation(message);\n                        _lastLineEndingCalled.TryPop(out string res);\n                        _lastLineEndingCalled.Push($\"{serialMessage}\");\n\n                        using (Serilog.Context.LogContext.PushProperty(\"result\", result))\n                            _logger.LogDebug(message + \" Results\");\n                    }\n                    catch (Exception e)\n                    {\n                        _logger.LogError(e, \"Error Performing Web Request\");\n                        result = $\"Error: {e.Message}\";\n                    }\n                }\n\n                return result;\n            }\n        }\n\n        public async Task<IEnumerable<string>> GetPorts()\n        {\n            try \n            {\n                IEnumerable<string> ports = await Task.Run(() => SerialPort.GetPortNames());\n                return ports;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Unable to retrieve serial ports\");\n                throw;\n            }\n        }\n\n        private void SetupSerialPort(string serialPort)\n        {\n        if (!string.IsNullOrEmpty(serialPort) && serialPort != \"null\")\n            {                \n                _port = new SerialPort();\n                _port.PortName = serialPort;\n                _port.ReadTimeout = 500;\n                _port.WriteTimeout = 500;\n                int baudRateInternal = 0;\n                if (int.TryParse(_appState.Config.LightSettings.LocalSerialHost.LocalSerialHostMainSetup.BaudRate, out baudRateInternal))\n                {\n                    _port.BaudRate = baudRateInternal;\n                }\n                else\n                {\n                    _port.BaudRate = 9600;\n                }\n\n                if (!_port.IsOpen)\n                {\n                    _port.Open();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/SetColor/SetColorCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n    public class SetColorCommand : IRequest<string>\n    {\n        public string Availability { get; set; }\n        public string Activity { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/SetColor/SetColorHandler.cs",
    "content": "﻿using MediatR;\nusing PresenceLight.Core;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n    internal class SetColorHandler : IRequestHandler<SetColorCommand, string>\n    {\n        readonly ILocalSerialHostService _service;\n        public SetColorHandler(ILocalSerialHostService service)\n        {\n            _service = service;\n        }\n\n        public async Task<string> Handle(SetColorCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.SetColor(command.Availability, command.Activity, cancellationToken);\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/GetGroups/GetGroupsCommand.cs",
    "content": "﻿using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    public class GetGroupsCommand : IRequest<IEnumerable<GroupedLight>>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/GetGroups/GetGroupsHandler.cs",
    "content": "﻿using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    internal class GetGroupsHandler : IRequestHandler<GetGroupsCommand, IEnumerable<GroupedLight>>\n    {\n        readonly IRemoteHueService _service;\n        public GetGroupsHandler(IRemoteHueService service)\n        {\n            _service = service;\n        }\n\n        public async Task<IEnumerable<GroupedLight>> Handle(GetGroupsCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.GetGroups();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/GetLights/GetLightsCommand.cs",
    "content": "﻿using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    public class GetLightsCommand : IRequest<IEnumerable<Light>>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/GetLights/GetLightsHandler.cs",
    "content": "﻿using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    internal class GetLightsHandler : IRequestHandler<GetLightsCommand, IEnumerable<Light>>\n    {\n        readonly IRemoteHueService _service;\n        public GetLightsHandler(IRemoteHueService service)\n        {\n            _service = service;\n        }\n\n        public async Task<IEnumerable<Light>> Handle(GetLightsCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.GetLights();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/RegisterBridge/RegisterBridgeCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    public class RegisterBridgeCommand : IRequest<(string bridgeId, string apiKey, string bridgeIp)>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/RegisterBridge/RegisterBridgeHandler.cs",
    "content": "﻿using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    internal class RegisterBridgeHandler : IRequestHandler<RegisterBridgeCommand, (string bridgeId, string apiKey, string bridgeIp)>\n    {\n        readonly IRemoteHueService _service;\n        public RegisterBridgeHandler(IRemoteHueService service)\n        {\n            _service = service;\n        }\n\n        public async Task<(string bridgeId, string apiKey, string bridgeIp)> Handle(RegisterBridgeCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.RegisterBridge();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/RemoteAuthenticationClient.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nusing JeffWilcox.Utilities.Silverlight;\n\nusing Newtonsoft.Json;\n\nusing Q42.HueApi.Interfaces;\nusing Q42.HueApi.Models;\n\nnamespace Q42.HueApi.Models\n{\n    public class AccessTokenResponseV2\n    {\n        public DateTimeOffset CreatedDate { get; set; }\n\n        public string access_token { get; set; }\n        public int expires_in { get; set; }\n        public string refresh_token { get; set; }\n        public string scope { get; set; }\n        public string token_type { get; set; }\n\n        public AccessTokenResponseV2()\n        {\n            CreatedDate = DateTimeOffset.UtcNow;\n        }\n        public DateTimeOffset AccessTokenExpireTime()\n        {\n            return CreatedDate.AddSeconds(expires_in);\n        }\n    }\n}\n\nnamespace Q42.HueApi.Interfaces\n{\n    public interface IRemoteAuthenticationClient\n    {\n        Uri BuildAuthorizeUri(string state, string deviceId, string? deviceName = null, string responseType = \"code\");\n\n        RemoteAuthorizeResponse ProcessAuthorizeResponse(string responseData);\n\n        /// <summary>\n        /// Initialize with existing AccessTokenResponse\n        /// </summary>\n        /// <param name=\"storedAccessToken\"></param>\n        void Initialize(AccessTokenResponseV2 storedAccessToken);\n\n        Task<AccessTokenResponseV2?> GetToken(string code);\n\n        Task<AccessTokenResponseV2?> RefreshToken(string refreshToken);\n\n        /// <summary>\n        /// Gets a valid access token\n        /// </summary>\n        /// <returns></returns>\n        Task<string?> GetValidToken();\n    }\n}\n\nnamespace Q42.HueApi\n{\n    /// <summary>\n    /// https://developers.meethue.com/develop/hue-api/remote-authentication-oauth/\n    /// </summary>\n    public class RemoteAuthenticationClientV2 : IRemoteAuthenticationClient\n    {\n        public bool IsInitialized { get; protected set; }\n\n        private readonly string _clientId;\n        private readonly string _clientSecret;\n        private readonly string _appId;\n\n        private AccessTokenResponseV2? _lastAuthorizationResponse;\n        private HttpClient _httpClient;\n\n        /// <summary>\n        /// \n        /// </summary>\n        /// <param name=\"clientId\">Identifies the client that is making the request. The value passed in this parameter must exactly match the value you receive from hue. Note that the underscore is not used in the clientid name of this parameter.</param>\n        /// <param name=\"clientSecret\">The clientsecret you have received from Hue when registering for the Hue Remote API.</param>\n        /// <param name=\"appId\">Identifies the app that is making the request. The value passed in this parameter must exactly match the value you receive from hue.</param>\n        public RemoteAuthenticationClientV2(string clientId, string clientSecret, string appId)\n        {\n            if (string.IsNullOrEmpty(clientId))\n                throw new ArgumentNullException(nameof(clientId));\n            if (string.IsNullOrEmpty(clientSecret))\n                throw new ArgumentNullException(nameof(clientSecret));\n            if (string.IsNullOrEmpty(appId))\n                throw new ArgumentNullException(nameof(appId));\n\n            _clientId = clientId;\n            _clientSecret = clientSecret;\n            _appId = appId;\n            _httpClient = new HttpClient();\n        }\n\n        public void Initialize(AccessTokenResponseV2 accessTokenResponse)\n        {\n            IsInitialized = true;\n            _lastAuthorizationResponse = accessTokenResponse;\n        }\n\n        /// <summary>\n        /// Authorization request\n        /// </summary>\n        /// <param name=\"state\">Provides any state that might be useful to your application upon receipt of the response. The Hue Authorization Server roundtrips this parameter, so your application receives the same value it sent. To mitigate against cross-site request forgery (CSRF), it is strongly recommended to include an anti-forgery token in the state, and confirm it in the response. One good choice for a state token is a string of 30 or so characters constructed using a high-quality random-number generator.</param>\n        /// <param name=\"deviceId\">The device identifier must be a unique identifier for the app or device accessing the Hue Remote API.</param>\n        /// <param name=\"deviceName\">The device name should be the name of the app or device accessing the remote API. The devicename is used in the user's \"My Apps\" overview in the Hue Account (visualized as: \"<app name> on <devicename>\"). If not present, deviceid is also used for devicename. The <app name> is the application name you provided to us the moment you requested access to the remote API.</param>\n        /// <param name=\"responseType\">The response_type value must be \"code\".</param>\n        /// <returns></returns>\n        public Uri BuildAuthorizeUri(string state, string deviceId, string? deviceName = null, string responseType = \"code\")\n        {\n            if (string.IsNullOrEmpty(responseType))\n                throw new ArgumentNullException(nameof(responseType));\n\n            string url = string.Format(\"https://api.meethue.com/v2/oauth2/authorize?client_id={0}&response_type={5}&state={1}&appid={3}&deviceid={2}&devicename={4}\", _clientId, state, deviceId, _appId, deviceName, responseType);\n\n            return new Uri(url);\n        }\n\n        public RemoteAuthorizeResponse ProcessAuthorizeResponse(string responseData)\n        {\n            string url = responseData;\n            string[] parts = url.Split(new char[] { '?', '&' });\n\n            RemoteAuthorizeResponse result = new RemoteAuthorizeResponse();\n\n            foreach (var part in parts)\n            {\n                string[] nv = part.Split(new char[] { '=' });\n                if (nv.Length == 2)\n                {\n                    if (nv[0].ToLower() == \"code\")\n                        result.Code = nv[1];\n                    if (nv[0].ToLower() == \"state\")\n                        result.State = nv[1];\n                }\n            }\n\n            return result;\n        }\n\n        /// <summary>\n        /// Get an access token\n        /// </summary>\n        /// <param name=\"code\">Code retreived using ProcessAuthorizeResponse</param>\n        /// <returns></returns>\n        public async Task<AccessTokenResponseV2?> GetToken(string code)\n        {\n            var requestUri = new Uri($\"https://api.meethue.com/v2/oauth2/token\");\n\n\n            var formParameters = new Dictionary<string, string> {\n        {\"code\", code},\n        {\"grant_type\", \"authorization_code\"}\n      };\n\n            var formContent = new FormUrlEncodedContent(formParameters);\n\n            //Do a token request\n            var responseTask = await _httpClient.PostAsync(requestUri, formContent).ConfigureAwait(false);\n            var responseString = responseTask.Headers.WwwAuthenticate.ToString();\n            responseString = responseString.Replace(\"Digest \", string.Empty);\n            string nonce = GetNonce(responseString);\n\n            if (!string.IsNullOrEmpty(nonce))\n            {\n                //Get token\n                var request = new HttpRequestMessage()\n                {\n                    RequestUri = requestUri,\n                    Method = HttpMethod.Post,\n                    Content = formContent\n                };\n\n                //Build request                \n                var credentials = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($\"{_clientId}:{_clientSecret}\"));\n                request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(\"Basic\", credentials);\n\n                var accessTokenResponse = await _httpClient.SendAsync(request).ConfigureAwait(false);\n                var accessTokenResponseString = await accessTokenResponse.Content.ReadAsStringAsync().ConfigureAwait(false);\n\n                var accessToken = JsonConvert.DeserializeObject<AccessTokenResponseV2>(accessTokenResponseString);\n\n                _lastAuthorizationResponse = accessToken;\n\n                return accessToken;\n\n            }\n\n\n            return null;\n        }\n\n        private static string GetNonce(string r)\n        {\n            //Find the nonce\n            int startNonce = r.IndexOf(\"nonce=\") + 7;\n            int endNonce = r.IndexOf(\"\\\"\", startNonce);\n            string nonce = r.Substring(startNonce, endNonce - startNonce);\n\n            return nonce;\n        }\n\n        public async Task<AccessTokenResponseV2?> RefreshToken(string refreshToken)\n        {\n            CheckInitialized();\n\n            var requestUri = new Uri(\"https://api.meethue.com/v2/oauth2/token\");\n\n            var formParameters = new Dictionary<string, string> {\n        {\"refresh_token\", refreshToken},\n        {\"grant_type\", \"refresh_token\"}\n      };\n\n            var formContent = new FormUrlEncodedContent(formParameters);\n\n            //Do a token request\n            var responseTask = await _httpClient.PostAsync(requestUri, formContent).ConfigureAwait(false);\n            var responseString = responseTask.Headers.WwwAuthenticate.ToString();\n            responseString = responseString.Replace(\"Digest \", string.Empty);\n            string nonce = GetNonce(responseString);\n\n            if (!string.IsNullOrEmpty(nonce))\n            {\n                //Get token\n                var request = new HttpRequestMessage()\n                {\n                    RequestUri = requestUri,\n                    Method = HttpMethod.Post,\n                    Content = formContent\n                };\n\n                //Build request\n                var credentials = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($\"{_clientId}:{_clientSecret}\"));\n                request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(\"Basic\", credentials);\n\n                var accessTokenResponse = await _httpClient.SendAsync(request).ConfigureAwait(false);\n                var accessTokenResponseString = await accessTokenResponse.Content.ReadAsStringAsync().ConfigureAwait(false);\n\n                var accessToken = JsonConvert.DeserializeObject<AccessTokenResponseV2>(accessTokenResponseString);\n\n                _lastAuthorizationResponse = accessToken;\n\n                return accessToken;\n            }\n\n            return null;\n        }\n\n        /// <summary>\n        /// Calculate hash for token request\n        /// </summary>\n        /// <param name=\"clientId\"></param>\n        /// <param name=\"clientSecret\"></param>\n        /// <param name=\"nonce\"></param>\n        /// <returns></returns>\n        private static string CalculateHash(string clientId, string clientSecret, string nonce, string path)\n        {\n            var HASH1 = MD5.GetMd5String($\"{clientId}:oauth2_client@api.meethue.com:{clientSecret}\");\n            var HASH2 = MD5.GetMd5String(\"POST:\" + path);\n            var response = MD5.GetMd5String(HASH1 + \":\" + nonce + \":\" + HASH2);\n\n            return response;\n        }\n\n        /// <summary>\n        /// Refreshes the token if needed\n        /// </summary>\n        /// <returns></returns>\n        public async Task<string?> GetValidToken()\n        {\n            CheckInitialized();\n            if (_lastAuthorizationResponse != null)\n            {\n                if (_lastAuthorizationResponse.AccessTokenExpireTime() > DateTimeOffset.UtcNow.AddMinutes(-5))\n                {\n                    return _lastAuthorizationResponse.access_token;\n                }\n                else\n                {\n                    var newToken = await this.RefreshToken(_lastAuthorizationResponse.refresh_token).ConfigureAwait(false);\n\n                    return newToken?.access_token;\n                }\n            }\n\n            throw new HueException(\"Unable to get access token. Access token and Refresh token expired.\");\n        }\n\n        /// <summary>\n        /// Check if the RemoteAuthenticationClient is initialized\n        /// </summary>\n        private void CheckInitialized()\n        {\n            if (!IsInitialized)\n                throw new InvalidOperationException(\"RemoteAuthenticationClient is not initialized. First call Initialize.\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/RemoteHueService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Net;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\n\nusing Newtonsoft.Json;\n\nusing Q42.HueApi;\nusing Q42.HueApi.ColorConverters;\nusing Q42.HueApi.ColorConverters.Original;\nusing Q42.HueApi.Interfaces;\nusing Q42.HueApi.Models;\n\nnamespace PresenceLight.Core\n{\n    public interface IRemoteHueService\n    {\n        Task SetColor(string availability, string activity, string lightId, string bridgeId);\n        Task<(string bridgeId, string apiKey, string bridgeIp)> RegisterBridge();\n        Task<IEnumerable<HueApi.Models.Light>> GetLights();\n        Task<IEnumerable<HueApi.Models.GroupedLight>> GetGroups();\n    }\n\n    public class RemoteHueService : IRemoteHueService\n    {\n        private AppState _appState;\n        private RemoteHueClient _client;\n        private IRemoteAuthenticationClient _authClient;\n        private readonly ILogger<RemoteHueService> _logger;\n        private MediatR.IMediator _mediator;\n        private string _cacheFile = $\"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\\\PresenceLight\\\\huetoken.cache\";\n\n        public RemoteHueService(AppState appState, ILogger<RemoteHueService> logger, MediatR.IMediator mediator)\n        {\n            _mediator = mediator;\n            _logger = logger;\n            _appState = appState;\n            CreateAuthClient();\n        }\n\n        private void CreateAuthClient()\n        {\n            if (Helpers.AreStringsNotEmpty(new string[]\n                    {   _appState.Config.LightSettings.Hue.RemoteHueClientId,\n                        _appState.Config.LightSettings.Hue.RemoteHueClientSecret,\n                        _appState.Config.LightSettings.Hue.RemoteHueClientAppName }))\n            {\n                _authClient = new RemoteAuthenticationClientV2(_appState.Config.LightSettings.Hue.RemoteHueClientId,\n                                                             _appState.Config.LightSettings.Hue.RemoteHueClientSecret,\n                                                             _appState.Config.LightSettings.Hue.RemoteHueClientAppName);\n            }\n            else\n            {\n                _logger.LogWarning(\"Remote Hue Not Configured Properly in Config\");\n            }\n        }\n\n        private async Task InitializeClient()\n        {\n            if (Helpers.AreStringsNotEmpty(new string[] {\n                                            _appState.Config.LightSettings.Hue.RemoteBridgeId,\n                                            _appState.Config.LightSettings.Hue.HueApiKey\n            }))\n            {\n                if (_authClient == null)\n                {\n                    CreateAuthClient();\n                }\n\n                if (_client == null || !_client.IsInitialized)\n                {\n                    try\n                    {\n                        var token = await _authClient.GetValidToken();\n                    }\n                    catch\n                    {\n                        if (File.Exists(_cacheFile))\n                        {\n                            AccessTokenResponseV2 response = JsonConvert.DeserializeObject<AccessTokenResponseV2>(File.ReadAllText(_cacheFile));\n                            if (response != null)\n                            {\n                                _authClient.Initialize(response);\n                            }\n                        }\n                        else\n                        {\n                            // prompt auth\n                            await RegisterBridge();\n                        }\n                    }\n\n                    _client = new RemoteHueClient(_authClient.GetValidToken);\n                    _client.Initialize(_appState.Config.LightSettings.Hue.RemoteBridgeId, _appState.Config.LightSettings.Hue.HueApiKey);\n                }\n            }\n        }\n\n        public async Task<(string bridgeId, string apiKey, string bridgeIp)> RegisterBridge()\n        {\n            try\n            {\n                await GetAccessToken();\n                var bridges = await _client.GetBridgesAsync();\n\n                string bridgeId;\n                string bridgeIp;\n                if (string.IsNullOrEmpty(_appState.Config.LightSettings.Hue.RemoteBridgeId))\n                {\n                    bridgeId = bridges.First().Id;\n                    bridgeIp = bridges.First().InternalIpaddress;\n                }\n                else\n                {\n                    bridgeId = _appState.Config.LightSettings.Hue.RemoteBridgeId;\n                    bridgeIp = _appState.Config.LightSettings.Hue.HueIpAddress;\n                }\n\n                string apiKey;\n                if (string.IsNullOrEmpty(_appState.Config.LightSettings.Hue.HueApiKey))\n                {\n                    apiKey = await _client.RegisterAsync(bridgeId, _appState.Config.LightSettings.Hue.RemoteHueClientAppName);\n                }\n                else\n                {\n                    apiKey = _appState.Config.LightSettings.Hue.HueApiKey;\n                }\n\n                if (!_client.IsInitialized)\n                {\n                    _client.Initialize(bridgeId, apiKey);\n                }\n\n                //Register app\n                return (bridgeId, apiKey, bridgeIp);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Registering Remote Bridge\");\n                throw;\n            }\n        }\n\n        public async Task SetColor(string availability, string activity, string lightId, string bridgeId)\n        {\n            await InitializeClient();\n\n            if (_client == null || !_client.IsInitialized)\n            {\n                _logger.LogInformation(\"Hue Client Not Initialized\");\n                return;\n            }\n\n            try\n            {\n                if (string.IsNullOrEmpty(lightId))\n                {\n                    _logger.LogInformation(\"Selected Hue Light Not Specified\");\n                    return;\n                }\n\n                var o = await Handle(_appState.Config.LightSettings.Hue.UseActivityStatus ? activity : availability, lightId);\n\n                if (o.returnFunc)\n                {\n                    return;\n                }\n\n                var color = o.color.Replace(\"#\", \"\");\n                var command = o.command;\n                var message = \"\";\n\n                switch (color.Length)\n                {\n                    case var length when color.Length == 6:\n                        // Do Nothing\n                        break;\n                    case var length when color.Length > 6:\n                        // Get last 6 characters\n                        color = color.Substring(0, 6);\n                        break;\n                    default:\n                        throw new ArgumentException(\"Supplied Color had an issue\");\n                }\n\n                command.SetColor(new RGBColor(color));\n\n\n                if (availability == \"Off\")\n                {\n                    command.On = false;\n\n                    if (lightId.Contains(\"group_id:\"))\n                    {\n                        await _client.SendGroupCommandAsync(command, lightId.Replace(\"group_id:\", \"\"));\n                    }\n                    else\n                    {\n                        await _client.SendCommandAsync(command, new List<string> { lightId.Replace(\"id:\", \"\") });\n                    }\n\n                    message = $\"Turning Hue Light {lightId} Off\";\n                    _logger.LogInformation(message);\n                    return;\n                }\n\n                if (_appState.Config.LightSettings.UseDefaultBrightness)\n                {\n                    if (_appState.Config.LightSettings.DefaultBrightness == 0)\n                    {\n                        command.On = false;\n                    }\n                    else\n                    {\n                        command.On = true;\n                        command.Brightness = Convert.ToByte(((Convert.ToDouble(_appState.Config.LightSettings.DefaultBrightness) / 100) * 254));\n                        command.TransitionTime = new TimeSpan(0);\n                    }\n                }\n                else\n                {\n                    if (_appState.Config.LightSettings.Hue.Brightness == 0)\n                    {\n                        command.On = false;\n                    }\n                    else\n                    {\n                        command.On = true;\n                        command.Brightness = Convert.ToByte(((Convert.ToDouble(_appState.Config.LightSettings.Hue.Brightness) / 100) * 254));\n                        command.TransitionTime = new TimeSpan(0);\n                    }\n                }\n\n                if (lightId.Contains(\"group_id:\"))\n                {\n                    await _client.SendGroupCommandAsync(command, lightId.Replace(\"group_id:\", \"\"));\n                }\n                else\n                {\n                    await _client.SendCommandAsync(command, new List<string> { lightId.Replace(\"id:\", \"\") });\n                }\n\n                message = $\"Setting Hue Light {lightId} to {color}\";\n                _logger.LogInformation(message);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Setting Color\");\n                throw;\n            }\n        }\n\n        public async Task<IEnumerable<HueApi.Models.Light>> GetLights()\n        {\n            await InitializeClient();\n\n            if (_client == null || !_client.IsInitialized)\n            {\n                _logger.LogInformation(\"Hue Client Not Initialized\");\n                return null;\n            }\n\n            try\n            {\n                var q42Lights = await _client.GetLightsAsync();\n                // if there are no lights, get some\n                if (!q42Lights.Any())\n                {\n                    await _client.SearchNewLightsAsync();\n                    Thread.Sleep(40000);\n                    q42Lights = await _client.GetNewLightsAsync();\n                }\n\n                // Convert Q42 Light models to HueApi Light models\n                var hueApiLights = new List<HueApi.Models.Light>();\n                foreach (var light in q42Lights)\n                {\n                    var guid = light.UniqueId?.Replace(\":\", \"-\");\n                    hueApiLights.Add(new HueApi.Models.Light\n                    {\n                        IdV1 = light.Id,\n                        Metadata = new HueApi.Models.Metadata\n                        {\n                            Name = light.Name\n                        }\n                    });\n                }\n                return hueApiLights;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Getting Lights\");\n                throw;\n            }\n        }\n\n        public async Task<IEnumerable<HueApi.Models.GroupedLight>> GetGroups()\n        {\n            await InitializeClient();\n\n            if (_client == null || !_client.IsInitialized)\n            {\n                _logger.LogInformation(\"Hue Client Not Initialized\");\n                return null;\n            }\n\n            try\n            {\n                var q42Groups = await _client.GetGroupsAsync();\n\n                // Convert Q42 Group models to HueApi GroupedLight models\n                var hueApiGroups = new List<HueApi.Models.GroupedLight>();\n                foreach (var group in q42Groups)\n                {\n                    hueApiGroups.Add(new HueApi.Models.GroupedLight\n                    {\n                        IdV1 = group.Id,\n                        Metadata = new HueApi.Models.Metadata\n                        {\n                            Name = group.Name\n                        }\n                    });\n                }\n                return hueApiGroups;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Getting Groups\");\n                throw;\n            }\n        }\n\n        public Task<string> FindBridge()\n        {\n            throw new NotImplementedException();\n        }\n\n        private async Task<(string color, LightCommand command, bool returnFunc)> Handle(string presence, string lightId)\n        {\n            var props = _appState.Config.LightSettings.Hue.Statuses.GetType().GetProperties().ToList();\n\n            if (_appState.Config.LightSettings.Hue.UseActivityStatus)\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"activity\")).ToList();\n            }\n            else\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"availability\")).ToList();\n            }\n\n            string color = \"\";\n            string message;\n            var command = new LightCommand();\n\n            if (presence.Contains(\"#\"))\n            {\n                // provided presence is actually a custom color\n                color = presence;\n                command.On = true;\n                return (color, command, false);\n            }\n\n            foreach (var prop in props)\n            {\n                if (presence == prop.Name.Replace(\"Status\", \"\").Replace(\"Availability\", \"\").Replace(\"Activity\", \"\"))\n                {\n                    var value = (AvailabilityStatus)prop.GetValue(_appState.Config.LightSettings.Hue.Statuses);\n\n                    if (!value.Disabled)\n                    {\n                        command.On = true;\n                        color = value.Color;\n                        return (color, command, false);\n                    }\n                    else\n                    {\n                        command.On = false;\n\n                        if (lightId.Contains(\"group_id:\"))\n                        {\n                            await _client.SendGroupCommandAsync(command, lightId.Replace(\"group_id:\", \"\"));\n                        }\n                        else\n                        {\n                            await _client.SendCommandAsync(command, new List<string> { lightId.Replace(\"id:\", \"\") });\n                        }\n                        message = $\"Turning Hue Light {lightId} Off\";\n                        _logger.LogInformation(message);\n                        return (color, command, true);\n                    }\n                }\n            }\n            return (color, command, false);\n        }\n\n        private async Task GetAccessToken()\n        {\n            try\n            {\n                Uri authorizeUri = _authClient.BuildAuthorizeUri(_appState.Config.LightSettings.Hue.RemoteHueClientAppName, _appState.Config.LightSettings.Hue.RemoteHueClientAppName);\n\n                TryBindListenerOnFreePort(out HttpListener http, out int port, out string redirectURI);\n\n                Helpers.OpenBrowser(authorizeUri.ToString());\n\n                // Waits for the OAuth authorization response.\n                var context = await http.GetContextAsync();\n\n                //Sends an HTTP response to the browser.\n                var response = context.Response;\n\n                string responseString = string.Format(\"<html><head><meta http-equiv='refresh' content='10;url=https://www.philips-hue.com/'></head><body>Please return to the app.</body></html>\");\n                var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);\n                response.ContentLength64 = buffer.Length;\n                var responseOutput = response.OutputStream;\n                Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>\n                {\n                    responseOutput.Close();\n                    http.Stop();\n                    Debug.WriteLine(\"HTTP server stopped.\");\n                });\n\n\n                // extracts the code\n                var code = context.Request.QueryString.Get(\"code\") ?? \"\";\n                var incoming_state = context.Request.QueryString.Get(\"state\");\n\n                var accessToken = await _authClient.GetToken(code);\n\n                if (accessToken != null)\n                {\n                    _authClient.Initialize(accessToken);\n                    if (File.Exists(_cacheFile))\n                    {\n                        File.Delete(_cacheFile);\n                    }\n                    await File.WriteAllTextAsync(_cacheFile, JsonConvert.SerializeObject(accessToken));\n                }\n\n                _client = new RemoteHueClient(_authClient.GetValidToken);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Processing Access Token for Remote Bridge\");\n                throw;\n            }\n        }\n\n        private static bool TryBindListenerOnFreePort(out HttpListener httpListener, out int port, out string uri)\n        {\n            // IANA suggested range for dynamic or private ports\n            const int MinPort = 49215;\n            const int MaxPort = 65535;\n\n            for (port = MinPort; port < MaxPort; port++)\n            {\n                httpListener = new HttpListener();\n                uri = $\"http://localhost:{port}/\";\n                httpListener.Prefixes.Add(uri);\n                try\n                {\n                    httpListener.Start();\n                    return true;\n                }\n                catch\n                {\n                    // nothing to do here -- the listener disposes itself when Start throws\n                }\n            }\n\n            port = 0;\n            uri = null;\n            httpListener = null;\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/SetColor/SetColorCommand.cs",
    "content": "﻿using MediatR;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    public class SetColorCommand : IRequest<Unit>\n    {\n        public string Availability { get; set; }\n        public string Activity { get; set; }\n        public string LightId { get; set; }\n        public string BridgeId { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/SetColor/SetColorHandler.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    internal class SetColorHandler : IRequestHandler<SetColorCommand, Unit>\n    {\n        readonly IRemoteHueService _service;\n        public SetColorHandler(IRemoteHueService service)\n        {\n            _service = service;\n        }\n\n        public async Task<Unit> Handle(SetColorCommand command, CancellationToken cancellationToken)\n        {\n            await _service.SetColor(command.Availability, command.Activity, command.LightId, command.BridgeId);\n            return default;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/ServicesExtensions.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\n\nnamespace PresenceLight.Core\n{\n    public static class ServicesExtensions\n    {\n        public static void AddPresenceServices(this IServiceCollection services)\n        {\n            services.AddSingleton<IWorkingHoursService, WorkingHoursService>();\n            services.AddSingleton<GraphWrapper>();\n            services.AddSingleton<IHueService, HueService>();\n            services.AddSingleton<IRemoteHueService, RemoteHueService>();\n            services.AddSingleton<LIFXService>();\n            services.AddSingleton<IYeelightService, YeelightService>();\n            services.AddSingleton<ICustomApiService, CustomApiService>();\n            services.AddSingleton<ILocalSerialHostService, LocalSerialHostService>();\n            services.AddSingleton<IWizService, WizService>();\n            services.AddSingleton<LIFXOAuthHelper, LIFXOAuthHelper>();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/GetLights/GetLightCommand.cs",
    "content": "﻿using System.Collections.Generic;\n\nusing MediatR;\n\nusing OpenWiz;\n\nnamespace PresenceLight.Core.WizServices\n{\n    public class GetLightCommand : IRequest<WizLight>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/GetLights/GetLightHandler.cs",
    "content": "﻿using MediatR;\n\nusing OpenWiz;\n\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.WizServices\n{\n    public class GetLightHandler : IRequestHandler<GetLightCommand, WizLight>\n    {\n        IWizService _service;\n        public GetLightHandler(IWizService service)\n        {\n            _service = service;\n        }\n\n        public async Task<WizLight> Handle(GetLightCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.GetLight();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/IPAddressExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Net.NetworkInformation;\nusing System.Net.Sockets;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.Lights.WizServices\n{\n    internal static class IPAddressExtensions\n    {\n        /// <summary>\n        /// Check if given IPv4 is a link-local (auto configuration) address (according to RFC3927)\n        /// </summary>\n        /// <remarks>https://tools.ietf.org/html/rfc3927</remarks>\n        /// <param name=\"ip\">The IPv4</param>\n        /// <returns>True if link-local, false otherwise</returns>\n        public static bool IsIPv4LinkLocal(this IPAddress ip)\n        {\n            if (ip.AddressFamily != AddressFamily.InterNetwork)\n            {\n                // Not an IPv4, simply return false\n                return false;\n            }\n\n            return ip.ToString().StartsWith(\"169.254.\");\n        }\n\n        /// <summary>\n        /// Check if given IP is a loopback\n        /// <para>This is just a helper extension around the static method IPAddress.IsLoopback</para>\n        /// </summary>\n        /// <param name=\"ip\">The IP</param>\n        /// <returns>True if loopback, False otherwise</returns>\n        public static bool IsLoopback(this IPAddress ip)\n        {\n            return IPAddress.IsLoopback(ip);\n        }\n\n        /// <summary>\n        /// Check if given IPv4 is in private range (according to RFC1918)\n        /// </summary>\n        /// <remarks>https://tools.ietf.org/html/rfc1918</remarks>\n        /// <param name=\"ip\">The IPv4</param>\n        /// <returns>True if private, false otherwise</returns>\n        public static bool IsIPv4Private(this IPAddress ip)\n        {\n            if (ip.AddressFamily != AddressFamily.InterNetwork)\n            {\n                // Not an IPv4, simply return false\n                return false;\n            }\n\n            byte[] bytes = ip.GetAddressBytes();\n\n            switch (bytes[0])\n            {\n                // 10.0.0.0 - 10.255.255.255 (10/8 prefix)\n                case 10:\n                    return true;\n\n                // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix)\n                case 172:\n                    return bytes[1] < 32 && bytes[1] >= 16;\n\n                // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\n                case 192:\n                    return bytes[1] == 168;\n\n                // Others\n                default:\n                    return false;\n            }\n        }\n\n        /// <summary>\n        /// Get a list of all IPv4 addresses in a specified network\n        /// </summary>\n        /// <exception cref=\"ArgumentException\">If IP or mask not IPv4</exception>\n        /// <param name=\"ip\">Any IP from the network</param>\n        /// <param name=\"mask\">The network mask (subnet)</param>\n        /// <returns>A list of IPAddress</returns>\n        public static IPAddress GetNetworkLoopbackAddress(this IPAddress ip, IPAddress mask)\n        {\n            if (ip.AddressFamily != AddressFamily.InterNetwork)\n            {\n                throw new ArgumentException(\"Not an IPv4 address\", nameof(ip));\n            }\n\n            if (mask.AddressFamily != AddressFamily.InterNetwork)\n            {\n                throw new ArgumentException(\"Not an IPv4 address\", nameof(mask));\n            }\n\n            List<IPAddress> range = new List<IPAddress>();\n\n            byte[] maskBytes = mask.GetAddressBytes();\n            byte[] ipBytes = ip.GetAddressBytes();\n\n            // Start IP (network IP) = IP AND MASK\n            byte[] startIpBytes = Enumerable.Range(0, 4)\n              .Select(i => (byte)(ipBytes[i] & maskBytes[i]))\n              .ToArray();\n\n            // Last IP (broadcast IP) = IP OR NOT MASK\n            byte[] endIpBytes = Enumerable.Range(0, 4)\n              .Select(i => (byte)(ipBytes[i] | ~maskBytes[i]))\n              .ToArray();\n\n            if (!Enumerable.Range(0, 4).Any(i => startIpBytes[i] > endIpBytes[i]))\n            {\n                for (int b0 = startIpBytes[0]; b0 <= endIpBytes[0]; b0++)\n                {\n                    for (int b1 = startIpBytes[1]; b1 <= endIpBytes[1]; b1++)\n                    {\n                        for (int b2 = startIpBytes[2]; b2 <= endIpBytes[2]; b2++)\n                        {\n                            for (int b3 = startIpBytes[3]; b3 <= endIpBytes[3]; b3++)\n                            {\n                                range.Add(new IPAddress(new byte[] { (byte)b0, (byte)b1, (byte)b2, (byte)b3 }));\n                            }\n                        }\n                    }\n                }\n            }\n            else\n            {\n                // Something went wrong : a start byte is above an end byte and thus will lead to bad results\n            }\n\n            return range.Last();\n        }\n    }\n\n    internal static class NetworkInterfaceExtensions\n    {\n        /// <summary>\n        /// Retrieve the list of all first private IPv4 of each interfaces that are up\n        /// </summary>\n        /// <returns>List of private IPv4 information</returns>\n        public static UnicastIPAddressInformation GetAllUpNetworkInterfacesFirstPrivateIPv4()\n        {\n            return NetworkInterface.GetAllNetworkInterfaces()\n                  // Keep only connected interfaces\n                  .Where(itf => itf.OperationalStatus == OperationalStatus.Up)\n                  // Retrieve all unicast addresses of each interface\n                  .SelectMany(itf => itf.GetIPProperties().UnicastAddresses)\n                  // Keep only private IPv4\n                  .Where(info => !info.Address.IsLoopback() && !info.Address.IsIPv4LinkLocal() && info.Address.IsIPv4Private() && info.PrefixOrigin == PrefixOrigin.Dhcp)\n                  .FirstOrDefault();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/SetColor/SetColorCommand.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.WizServices\n{\n    public class SetColorCommand : IRequest<Unit>\n    {\n        public string Availability { get; set; }\n        public string Activity { get; set; }\n        public string LightID { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/SetColor/SetColorHandler.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.WizServices.WizService\n{\n    public class SetColorHandler : IRequestHandler<SetColorCommand, Unit>\n    {\n        IWizService _service;\n        public SetColorHandler(IWizService hueService)\n        {\n            _service = hueService;\n        }\n\n        public  async Task<Unit> Handle(SetColorCommand command, CancellationToken cancellationToken)\n        {\n            await _service.SetColor(command.Availability, command.Activity, command.LightID);\n            return default;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/WizLight.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.WizServices\n{\n    public class WizLight\n    {\n        public string LightName { get; set; }\n        public string MacAddress { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/WizService.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Net;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\n\nusing OpenWiz;\n\nusing PresenceLight.Core.WizServices;\n\nusing HueApi.ColorConverters;\n\nnamespace PresenceLight.Core\n{\n    public interface IWizService\n    {\n        Task<WizLight> GetLight();\n\n        Task SetColor(string availability, string activity, string lightId);\n    }\n    public class WizService : IWizService\n    {\n        private AppState _appState;\n        private readonly ILogger<WizService> _logger;\n\n        public WizService(AppState appState, ILogger<WizService> logger)\n        {\n            _logger = logger;\n            _appState = appState;\n        }\n\n        public WizService(AppState appState)\n        {\n            _appState = appState;\n        }\n\n        public async Task<WizLight> GetLight()\n        {\n            WizSocket socket = new WizSocket();\n            socket.GetSocket().EnableBroadcast = true; // This will enable sending to the broadcast address\n\n            WizHandle handle = new WizHandle(\"000000000000\", IPAddress.Parse(_appState.Config.LightSettings.Wiz.IPAddress)); // MAC doesn't matter here\n\n            WizState state = WizState.MakeGetSystemConfig();\n\n\n            socket.GetSocket().ReceiveTimeout = 10000; // This will prevent the demo from running indefinitely\n\n\n            await Task.Run(() => socket.SendTo(state, handle));\n\n            WizLight light = new WizLight();\n\n            // You won't easily get an IP address here, but this will list all Home IDs on the network.\n            while (true)\n            {\n                try\n                {\n                    state = socket.ReceiveFrom(handle);\n                    if (!string.IsNullOrEmpty(state.Result.ModuleName) && !string.IsNullOrEmpty(state.Result.Mac))\n                    {\n                        Console.WriteLine($\"Home ID for light {state.Result.Mac} = {state.Result.HomeId}\");\n\n                        light.MacAddress = state.Result.Mac;\n                        light.LightName = state.Result.ModuleName;\n                    }\n                    break;\n                }\n                catch (Exception e)\n                {\n                }\n            }\n            return light;\n\n        }\n\n        public async Task SetColor(string availability, string activity, string lightId)\n        {\n            if (string.IsNullOrEmpty(lightId))\n            {\n                _logger.LogInformation(\"Selected Wiz Light Not Specified\");\n                return;\n            }\n\n            try\n            {\n                var o = await Handle(_appState.Config.LightSettings.Wiz.UseActivityStatus ? activity : availability, lightId);\n\n                if (o.returnFunc)\n                {\n                    return;\n                }\n\n                var color = o.color.Replace(\"#\", \"\");\n                var command = o.command;\n                var message = \"\";\n                switch (color.Length)\n                {\n                    case var length when color.Length == 6:\n                        // Do Nothing\n                        break;\n                    case var length when color.Length > 6:\n                        // Get last 6 characters\n                        color = color.Substring(0, 6);\n                        break;\n                    default:\n                        throw new ArgumentException(\"Supplied Color had an issue\");\n                }\n\n                var rgb = new RGBColor(color);\n\n                command.R = Convert.ToInt32(rgb.R);\n                command.B = Convert.ToInt32(rgb.B);\n                command.G = Convert.ToInt32(rgb.G);\n\n                if (availability == \"Off\")\n                {\n                    command.State = false;\n\n                    await UpdateLight(command, lightId);\n                    message = $\"Turning Wiz Light {lightId} Off\";\n                    _logger.LogInformation(message);\n                    return;\n                }\n\n                if (_appState.Config.LightSettings.UseDefaultBrightness)\n                {\n                    if (_appState.Config.LightSettings.DefaultBrightness == 0)\n                    {\n                        command.State = false;\n                    }\n                    else\n                    {\n                        command.State = true;\n                        command.Dimming = _appState.Config.LightSettings.DefaultBrightness;\n                        command.Speed = 0;\n                    }\n                }\n                else\n                {\n                    if (_appState.Config.LightSettings.Wiz.Brightness == 0)\n                    {\n                        command.State = false;\n                    }\n                    else\n                    {\n                        command.State = true;\n                        command.Dimming = _appState.Config.LightSettings.Wiz.Brightness;\n                        command.Speed = 0;\n                    }\n                }\n\n                await UpdateLight(command, lightId);\n\n                message = $\"Setting Wiz Light {lightId} to {color}\";\n                _logger.LogInformation(message);\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Setting Color\");\n                throw;\n            }\n        }\n\n        private async Task<(string color, WizParams command, bool returnFunc)> Handle(string presence, string lightId)\n        {\n            var props = _appState.Config.LightSettings.Wiz.Statuses.GetType().GetProperties().ToList();\n\n            if (_appState.Config.LightSettings.Wiz.UseActivityStatus)\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"activity\")).ToList();\n            }\n            else\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"availability\")).ToList();\n            }\n\n            string color = \"\";\n            string message;\n\n            var command = new WizParams();\n\n            if (presence.Contains('#'))\n            {\n                // provided presence is actually a custom color\n                color = presence;\n                command.State = true;\n                return (color, command, false);\n            }\n\n            foreach (var prop in props)\n            {\n                if (presence == prop.Name.Replace(\"Status\", \"\").Replace(\"Availability\", \"\").Replace(\"Activity\", \"\"))\n                {\n                    var value = (AvailabilityStatus)prop.GetValue(_appState.Config.LightSettings.Wiz.Statuses);\n\n                    if (!value.Disabled)\n                    {\n                        command.State = true;\n                        color = value.Color;\n                        return (color, command, false);\n                    }\n                    else\n                    {\n                        command.State = false;\n                        await UpdateLight(command, lightId);\n                        message = $\"Turning Wiz Light {lightId} Off\";\n                        _logger.LogInformation(message);\n                        return (color, command, true);\n                    }\n                }\n            }\n            return (color, command, false);\n        }\n\n\n        private async Task<WizResult> UpdateLight(WizParams wizParams, string lightId)\n        {\n            WizSocket socket = new WizSocket();\n            socket.GetSocket().EnableBroadcast = true; // This will enable sending to the broadcast address\n            socket.GetSocket().ReceiveTimeout = 1000; // This will prevent the demo from running indefinitely\n            WizHandle handle = new WizHandle(lightId, IPAddress.Parse(_appState.Config.LightSettings.Wiz.IPAddress)); // MAC doesn't matter here\n\n            WizState state = new WizState\n            {\n                Method = WizMethod.setPilot,\n                Params = wizParams\n            };\n\n            await Task.Run(() => socket.SendTo(state, handle));\n\n            WizResult pilot;\n            while (true)\n            {\n                state = socket.ReceiveFrom(handle);\n                pilot = state.Result;\n                break;\n            }\n\n            return pilot;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/IsInWorkingHours/IsInWorkingHoursCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.WorkingHoursServices\n{\n    public class IsInWorkingHoursCommand : IRequest<bool>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/IsInWorkingHours/IsInWorkingHoursHandler.cs",
    "content": "﻿using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.WorkingHoursServices\n{\n    internal class IsInWorkingHoursHandler : IRequestHandler<IsInWorkingHoursCommand, bool>\n    {\n        IWorkingHoursService _service;\n        public IsInWorkingHoursHandler(IWorkingHoursService service)\n        {\n            _service = service;\n        }\n\n        public async Task<bool> Handle(IsInWorkingHoursCommand command, CancellationToken cancellationToken)\n        {\n            \n            return await Task.FromResult(_service.IsInWorkingHours());\n\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/UseWorkingHours/UseWorkingHoursCommand.cs",
    "content": "﻿using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.WorkingHoursServices\n{\n    public class UseWorkingHoursCommand : IRequest<bool>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/UseWorkingHours/UseWorkingHoursHandler.cs",
    "content": "﻿using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.WorkingHoursServices\n{\n    internal class UseWorkingHoursHandler : IRequestHandler<UseWorkingHoursCommand, bool>\n    {\n        IWorkingHoursService _service;\n        public UseWorkingHoursHandler(IWorkingHoursService service)\n        {\n            _service = service;\n        }\n\n        public async Task<bool> Handle(UseWorkingHoursCommand command, CancellationToken cancellationToken)\n        {\n            \n            return await Task.FromResult(_service.UseWorkingHours());\n\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/WorkingHoursService.cs",
    "content": "﻿using System;\nusing System.Globalization;\n\nnamespace PresenceLight.Core\n{\n    public interface IWorkingHoursService\n    {\n        public bool UseWorkingHours();\n\n        public bool IsInWorkingHours();\n    }\n\n    public class WorkingHoursService : IWorkingHoursService\n\n    {\n        private readonly AppState _appState;\n\n        public WorkingHoursService(AppState appState)\n        {\n            _appState = appState;\n        }\n\n\n        /// <summary>\n        /// Exposes a config value should you want to short circuit the working hours test.\n        /// </summary>\n        public bool UseWorkingHours()\n        {\n\n            return _appState.Config.LightSettings.UseWorkingHours;\n\n        }\n        public bool IsInWorkingHours()\n        {\n\n            bool IsWorkingHours = false;\n\n            if (!Helpers.AreStringsNotEmpty(new string[] {_appState.Config.LightSettings.WorkingHoursStartTime,\n                                            _appState.Config.LightSettings.WorkingHoursEndTime,\n                                            _appState.Config.LightSettings.WorkingDays}))\n            {\n                IsWorkingHours = false;\n                return false;\n            }\n\n            if (!_appState.Config.LightSettings.WorkingDays.Contains(DateTime.Now.DayOfWeek.ToString(), StringComparison.OrdinalIgnoreCase))\n            {\n                IsWorkingHours = false;\n                return false;\n            }\n\n            // convert datetime to a TimeSpan\n            bool validStart = TimeSpan.TryParse(_appState.Config.LightSettings.WorkingHoursStartTime, out TimeSpan start);\n            bool validEnd = TimeSpan.TryParse(_appState.Config.LightSettings.WorkingHoursEndTime, out TimeSpan end);\n            if (!validEnd || !validStart)\n            {\n                IsWorkingHours = false;\n                return false;\n            }\n\n            TimeSpan now = DateTime.Now.TimeOfDay;\n            // see if start comes before end\n            if (start < end)\n            {\n                IsWorkingHours = start <= now && now <= end;\n                return IsWorkingHours;\n            }\n            // start is after end, so do the inverse comparison\n\n            IsWorkingHours = !(end < now && now < start);\n\n            return IsWorkingHours;\n\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/FindLights/FindLightsCommand.cs",
    "content": "﻿using MediatR;\nusing YeelightAPI;\n\nnamespace PresenceLight.Core.YeelightServices\n{\n    public class GetLightCommand : IRequest<DeviceGroup>\n    {\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/FindLights/FindLightsHandler.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing YeelightAPI;\n\nnamespace PresenceLight.Core.YeelightServices\n{\n    internal class GetLightsHandler : IRequestHandler<GetLightCommand, DeviceGroup>\n    {\n        IYeelightService _service;\n        public GetLightsHandler(IYeelightService service)\n        {\n            _service = service;\n        }\n\n        public async Task<DeviceGroup> Handle(GetLightCommand command, CancellationToken cancellationToken)\n        {\n            return await _service.FindLights();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/SetColor/SetColorCommand.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.YeelightServices\n{\n    public class SetColorCommand : IRequest<Unit>\n    {\n        public string Availability { get; set; }\n        public string Activity { get; set; }\n        public string LightId { get; set; }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/SetColor/SetColorHandler.cs",
    "content": "﻿using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.YeelightServices\n{\n    internal class SetColorHandler : IRequestHandler<SetColorCommand, Unit>\n    {\n        IYeelightService _service;\n        public SetColorHandler(IYeelightService service)\n        {\n            _service = service;\n        }\n\n        public async Task<Unit> Handle(SetColorCommand command, CancellationToken cancellationToken)\n        {\n            await _service.SetColor(command.Availability, command.Activity, command.LightId);\n            return default;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/YeelightService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing YeelightAPI;\nusing HueApi.ColorConverters;\nusing Microsoft.Extensions.Logging;\n\nnamespace PresenceLight.Core\n{\n    public interface IYeelightService\n    {\n        Task SetColor(string availability, string activity, string lightId);\n        Task<DeviceGroup> FindLights();\n    }\n    public class YeelightService : IYeelightService\n    {\n        private AppState _appState;\n\n        private MediatR.IMediator _mediator;\n        private DeviceGroup deviceGroup;\n        private readonly ILogger<YeelightService> _logger;\n\n        public YeelightService(AppState appState, ILogger<YeelightService> logger, MediatR.IMediator mediator)\n        {\n            _logger = logger;\n            _appState = appState;\n            _mediator = mediator;\n        }\n\n        public void Initialize(AppState appState)\n        {\n            _appState = appState;\n        }\n\n        public async Task SetColor(string availability, string activity, string lightId)\n        {\n            string message = \"\";\n\n            if (string.IsNullOrEmpty(lightId))\n            {\n                _logger.LogInformation(\"Selected Yeelight Light Not Specified\");\n                return;\n            }\n\n            var o = await Handle(_appState.Config.LightSettings.Yeelight.UseActivityStatus ? activity : availability, lightId);\n\n            if (o.returnFunc)\n            {\n                return;\n            }\n\n            if (o.device == null)\n            {\n                message = $\"Yeelight Device {lightId} Not Found\";\n                _logger.LogError(message);\n                throw new ArgumentOutOfRangeException(nameof(lightId), message);\n            }\n\n            o.device.OnNotificationReceived += Device_OnNotificationReceived;\n            o.device.OnError += Device_OnError;\n\n            if (!await o.device.Connect())\n            {\n                message = $\"Unable to Connect to Yeelight Device {lightId}\";\n                _logger.LogError(message);\n                throw new ArgumentOutOfRangeException(nameof(lightId), message);\n            }\n\n            try\n            {\n                var color = o.color.Replace(\"#\", \"\");\n\n                switch (color.Length)\n                {\n                    case var length when color.Length == 6:\n                        // Do Nothing\n                        break;\n                    case var length when color.Length > 6:\n                        // Get last 6 characters\n                        color = color.Substring(0, 6);\n                        break;\n                    default:\n                        throw new ArgumentException(\"Supplied Color had an issue\");\n                }\n\n                if (availability == \"Off\")\n                {\n                    await o.device.TurnOff();\n\n                    message = $\"Turning Yeelight Light {lightId} Off\";\n                    _logger.LogInformation(message);\n                    return;\n                }\n\n                if (_appState.Config.LightSettings.UseDefaultBrightness)\n                {\n                    if (_appState.Config.LightSettings.DefaultBrightness == 0)\n                    {\n                        await o.device.TurnOff();\n                    }\n                    else\n                    {\n                        await o.device.TurnOn();\n                        await o.device.SetBrightness(Convert.ToInt32(_appState.Config.LightSettings.DefaultBrightness));\n                    }\n                }\n                else\n                {\n                    if (_appState.Config.LightSettings.Hue.Brightness == 0)\n                    {\n                        await o.device.TurnOff();\n                    }\n                    else\n                    {\n                        await o.device.TurnOn();\n                        await o.device.SetBrightness(Convert.ToInt32(_appState.Config.LightSettings.Yeelight.Brightness));\n                    }\n                }\n\n                var rgb = new RGBColor(color);\n                await o.device.SetRGBColor((int)rgb.R, (int)rgb.G, (int)rgb.B);\n                return;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Setting Color\");\n                throw;\n            }\n        }\n        private void Device_OnError(object sender, UnhandledExceptionEventArgs e)\n        {\n            //throw new NotImplementedException();\n        }\n\n        private void Device_OnNotificationReceived(object sender, NotificationReceivedEventArgs e)\n        {\n            //throw new NotImplementedException();\n        }\n\n        public async Task<DeviceGroup> FindLights()\n        {\n            try\n            {\n                IEnumerable<Device> devices = await DeviceLocator.DiscoverAsync();\n                this.deviceGroup = new DeviceGroup(devices);\n                return this.deviceGroup;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error Occurred Finding Lights\");\n                throw;\n            }\n        }\n\n        private async Task<(string color, Device device, bool returnFunc)> Handle(string presence, string lightId)\n        {\n            var props = _appState.Config.LightSettings.Yeelight.Statuses.GetType().GetProperties().ToList();\n\n            if (_appState.Config.LightSettings.Yeelight.UseActivityStatus)\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"activity\")).ToList();\n            }\n            else\n            {\n                props = props.Where(a => a.Name.ToLower().StartsWith(\"availability\")).ToList();\n            }\n\n            string color = \"\";\n            string message;\n            var device = this.deviceGroup.FirstOrDefault(x => x.Id == lightId);\n\n            if (device != null)\n            {\n                device.OnNotificationReceived += Device_OnNotificationReceived;\n                device.OnError += Device_OnError;\n\n                await device.Connect();\n\n                if (presence.Contains(\"#\"))\n                {\n                    // provided presence is actually a custom color\n                    color = presence;\n                    await device.TurnOn();\n                    return (color, device, false);\n                }\n\n                foreach (var prop in props)\n                {\n                    if (presence == prop.Name.Replace(\"Status\", \"\").Replace(\"Availability\", \"\").Replace(\"Activity\", \"\"))\n                    {\n                        var value = (AvailabilityStatus)prop.GetValue(_appState.Config.LightSettings.Yeelight.Statuses);\n\n                        if (!value.Disabled)\n                        {\n                            await device.TurnOn();\n                            color = value.Color;\n                            return (color, device, false);\n                        }\n                        else\n                        {\n                            await device.TurnOff();\n\n                            message = $\"Turning Yeelight Light {lightId} Off\";\n                            _logger.LogInformation(message);\n                            return (color, device, true);\n                        }\n                    }\n                }\n            }\n            return (color, device, false);\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "src/PresenceLight.Core/Logging/ILoggerExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.CompilerServices;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\n\n/// <summary>\n/// This class is purposefully put without a namespace so that it overrides the existing ILogger extensions\n/// to allow the additional context properties and log messaging to be written to the logs.\n/// </summary>\npublic static class ILoggerExtensions\n{\n\n    /// <summary>\n    /// Formats and writes a log message at the specified log level.\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"logLevel\">Entry will be written on this level.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void Log(this ILogger logger,\n                                      LogLevel logLevel,\n                                      EventId eventId,\n                                      Exception exception,\n                                      string message,\n                                      [CallerMemberName] string memberName = \"\",\n                                      [CallerFilePath] string fileName = \"\",\n                                      [CallerLineNumber] int lineNumber = 0,\n                                      params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.Log(logLevel, eventId, exception, message, args);\n        }\n    }\n\n\n \n    /// <summary>\n    /// Formats and writes a log message at the specified log level.\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"logLevel\">Entry will be written on this level.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void Log(this ILogger logger,\n                                    LogLevel logLevel,\n                                    EventId eventId,\n                                    string message,\n                                      [CallerMemberName] string memberName = \"\",\n                                      [CallerFilePath] string fileName = \"\",\n                                      [CallerLineNumber] int lineNumber = 0,\n                                    params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.Log(logLevel, eventId, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes a log message at the specified log level.\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"logLevel\">Entry will be written on this level.</param> \n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void Log(this ILogger logger,\n                                      LogLevel logLevel,\n                                      Exception exception,\n                                      string message,\n                                      [CallerMemberName] string memberName = \"\",\n                                      [CallerFilePath] string fileName = \"\",\n                                      [CallerLineNumber] int lineNumber = 0, params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.Log(logLevel, exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes a log message at the specified log level.\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"logLevel\">Entry will be written on this level.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void Log(this ILogger logger,\n                                    LogLevel logLevel,\n                                    string message,\n                                    [CallerMemberName] string memberName = \"\",\n                                    [CallerFilePath] string fileName = \"\",\n                                    [CallerLineNumber] int lineNumber = 0, params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.Log(logLevel, message, args);\n        }\n\n    }\n\n    /// <summary>\n    /// Formats and writes a critical log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogCritical(this ILogger logger,\n                                            EventId eventId,\n                                            Exception exception,\n                                            string message,\n                                            [CallerMemberName] string memberName = \"\",\n                                            [CallerFilePath] string fileName = \"\",\n                                            [CallerLineNumber] int lineNumber = 0,\n                                            params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogCritical(eventId, exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes a critical log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogCritical(this ILogger logger,\n                                            EventId eventId,\n                                            string message,\n                                            [CallerMemberName] string memberName = \"\",\n                                            [CallerFilePath] string fileName = \"\",\n                                            [CallerLineNumber] int lineNumber = 0,\n                                            params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogCritical(eventId, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes a critical log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogCritical(this ILogger logger,\n                                            Exception exception,\n                                            string message,\n                                            [CallerMemberName] string memberName = \"\",\n                                            [CallerFilePath] string fileName = \"\",\n                                            [CallerLineNumber] int lineNumber = 0,\n                                            params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogCritical(exception, message, args);\n        }\n    }\n\n\n    /// <summary>\n    /// Formats and writes a critical log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogCritical(this ILogger logger,\n                                            string message,\n                                            [CallerMemberName] string memberName = \"\",\n                                            [CallerFilePath] string fileName = \"\",\n                                            [CallerLineNumber] int lineNumber = 0,\n                                            params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogCritical(message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes a debug log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogDebug(this ILogger logger,\n                                         EventId eventId,\n                                         Exception exception,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogDebug(eventId, exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes a debug log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogDebug(this ILogger logger,\n                                         EventId eventId,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogDebug(eventId, message, args);\n        }\n    }\n    ///<summary>\n    /// Formats and writes a debug log message\n    /// Enhanced for PresenceLight with extended Context Logging\n    /// </summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogDebug(this ILogger logger,\n                                     Exception exception,\n                                     string message,\n                                     [CallerMemberName] string memberName = \"\",\n                                     [CallerFilePath] string fileName = \"\",\n                                     [CallerLineNumber] int lineNumber = 0,\n                                     params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogDebug(exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes an error log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogDebug(this ILogger logger,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogDebug(message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes an error  log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogError(this ILogger logger,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogError(message, args);\n        }\n    }\n    //\n    // Summary:\n    //     Formats and writes an error log message.\n    //     Enhanced for PresenceLight with extended Context Logging\n    //\n    // Parameters:\n    //   logger:\n    //     The Microsoft.Extensions.Logging.ILogger to write to.\n    //\n    //   exception:\n    //     The exception to log.\n    //\n    //   message:\n    //     Format string of the log message in message template format. Example: \"User {User}\n    //     logged in from {Address}\"\n    //\n    //   args:\n    //     An object array that contains zero or more objects to format.\n\n    /// <summary>\n    /// Formats and writes an error  log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogError(this ILogger logger,\n                                     Exception exception,\n                                     string message,\n                                     [CallerMemberName] string memberName = \"\",\n                                     [CallerFilePath] string fileName = \"\",\n                                     [CallerLineNumber] int lineNumber = 0,\n                                     params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogError(exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes an error  log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogError(this ILogger logger,\n                                         EventId eventId,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogError(eventId, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes an error  log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogError(this ILogger logger,\n                                         EventId eventId,\n                                         Exception exception,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogError(eventId, exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes an information log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogInformation(this ILogger logger,\n                                               EventId eventId,\n                                               Exception exception,\n                                               string message,\n                                               [CallerMemberName] string memberName = \"\",\n                                               [CallerFilePath] string fileName = \"\",\n                                               [CallerLineNumber] int lineNumber = 0,\n                                               params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogInformation(eventId, exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes an information log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogInformation(this ILogger logger,\n                                               EventId eventId,\n                                               string message,\n                                               [CallerMemberName] string memberName = \"\",\n                                               [CallerFilePath] string fileName = \"\",\n                                               [CallerLineNumber] int lineNumber = 0,\n                                               params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogInformation(eventId, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes an information log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogInformation(this ILogger logger,\n                                               Exception exception,\n                                               string message,\n                                               [CallerMemberName] string memberName = \"\",\n                                               [CallerFilePath] string fileName = \"\",\n                                               [CallerLineNumber] int lineNumber = 0,\n                                               params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogInformation(exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes an information log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogInformation(this ILogger logger,\n                                               string message,\n                                               [CallerMemberName] string memberName = \"\",\n                                               [CallerFilePath] string fileName = \"\",\n                                               [CallerLineNumber] int lineNumber = 0,\n                                               params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogInformation(message, args);\n        }\n    }\n\n\n    /// <summary>\n    /// Formats and writes a trace log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogTrace(this ILogger logger,\n                                         EventId eventId,\n                                         Exception exception,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogTrace(eventId, exception, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes a trace log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogTrace(this ILogger logger,\n                                         EventId eventId,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogTrace(eventId, message, args);\n        }\n    }\n\n    /// <summary>\n    /// Formats and writes a trace log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogTrace(this ILogger logger,\n                                         Exception exception,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogTrace(exception, message, args);\n        }\n    }\n     \n    /// <summary>\n    /// Formats and writes a trace log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogTrace(this ILogger logger,\n                                         string message,\n                                         [CallerMemberName] string memberName = \"\",\n                                         [CallerFilePath] string fileName = \"\",\n                                         [CallerLineNumber] int lineNumber = 0,\n                                         params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogTrace(message, args);\n        }\n    }\n\n     \n    /// <summary>\n    /// Formats and writes a warning log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogWarning(this ILogger logger,\n                                       EventId eventId,\n                                       Exception exception,\n                                       string message,\n                                       [CallerMemberName] string memberName = \"\",\n                                       [CallerFilePath] string fileName = \"\",\n                                       [CallerLineNumber] int lineNumber = 0,\n                                       params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogWarning(eventId, exception, message, args);\n        }\n    }\n \n    /// <summary>\n    /// Formats and writes a warning log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"eventId\">The event id associated with the log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogWarning(this ILogger logger,\n                                       EventId eventId,\n                                       string message,\n                                       [CallerMemberName] string memberName = \"\",\n                                       [CallerFilePath] string fileName = \"\",\n                                       [CallerLineNumber] int lineNumber = 0,\n                                       params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogWarning(eventId, message, args);\n        }\n    }\n    /// <summary>\n    /// Formats and writes a warning log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n        /// <param name=\"exception\">The exception to log.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogWarning(this ILogger logger,\n                                       Exception exception,\n                                       string message,\n                                       [CallerMemberName] string memberName = \"\",\n                                       [CallerFilePath] string fileName = \"\",\n                                       [CallerLineNumber] int lineNumber = 0,\n                                       params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogWarning(exception, message, args);\n        }\n    }\n    \n    /// <summary>\n    /// Formats and writes a warning log message\n    /// Enhanced for PresenceLight with extended Context Logging</summary>\n    /// <param name=\"logger\">The Microsoft.Extensions.Logging.ILogger to write to.</param>\n    /// <param name=\"message\">Format string of the log message.</param>\n    /// <param name=\"memberName\">Membername occurring Note:  Injected!</param>\n    /// <param name=\"fileName\">File name where occurring Note:  Injected!</param>\n    /// <param name=\"lineNumber\">LineNumber  where occurring Note:  Injected!</param>\n    /// <param name=\"args\">An object array that contains zero or more objects to format.</param>\n    public static void LogWarning(this ILogger logger,\n                                       string message,\n                                       [CallerMemberName] string memberName = \"\",\n                                       [CallerFilePath] string fileName = \"\",\n                                       [CallerLineNumber] int lineNumber = 0,\n                                       params object[] args)\n    {\n        using (Serilog.Context.LogContext.PushProperty(\"MemberName\", memberName))\n        using (Serilog.Context.LogContext.PushProperty(\"FilePath\", fileName))\n        using (Serilog.Context.LogContext.PushProperty(\"LineNumber\", lineNumber))\n        {\n            message = $\"{message} - {fileName.Split(\"\\\\\").LastOrDefault().Replace(\".cs\", \"\")}:{memberName} Line: {lineNumber}\";\n\n            logger.LogWarning(message, args);\n        }\n    }\n}\n\n"
  },
  {
    "path": "src/PresenceLight.Core/Logging/PresenceEventsLogSink.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing Serilog;\nusing Serilog.Configuration;\nusing Serilog.Core;\nusing Serilog.Events;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents a log sink for presence events.\n    /// </summary>\n    public class PresenceEventsLogSink : ILogEventSink\n    {\n        private readonly IFormatProvider _formatProvider;\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"PresenceEventsLogSink\"/> class with the specified format provider.\n        /// </summary>\n        /// <param name=\"formatProvider\">The format provider to be used for formatting log messages.</param>\n        public PresenceEventsLogSink(IFormatProvider formatProvider)\n        {\n            _formatProvider = formatProvider;\n        }\n\n        /// <summary>\n        /// Emits a log event by invoking the PresenceEventsLogHandler delegate.\n        /// </summary>\n        /// <param name=\"logEvent\">The log event to emit.</param>\n        public void Emit(LogEvent logEvent)\n        {\n            PresenceEventsLogHandler?.Invoke(this, logEvent);\n        }\n\n        /// <summary>\n        /// Represents the event handler for presence events logging.\n        /// </summary>\n        public static EventHandler<LogEvent> PresenceEventsLogHandler;\n    }\n\n\n    /// <summary>\n    /// Provides extension methods for adding the <see cref=\"PresenceEventsLogSink\"/> to the logger configuration.\n    /// </summary>\n    public static class PresenceEventsLogSinkExtensions\n    {\n\n        /// <summary>\n        /// Adds the <see cref=\"PresenceEventsLogSink\"/> to the logger configuration.\n        /// </summary>\n        /// <param name=\"loggerConfiguration\">The logger configuration.</param>\n        /// <param name=\"formatProvider\">The format provider.</param>\n        /// <returns>The logger configuration with the added <see cref=\"PresenceEventsLogSink\"/>.</returns>\n        public static LoggerConfiguration PresenceEventsLogSink(\n              this LoggerSinkConfiguration loggerConfiguration,\n              IFormatProvider formatProvider = null)\n        {\n            return loggerConfiguration.Sink(new PresenceEventsLogSink(formatProvider));\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Core/PresenceLight.Core.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <LangVersion>latest</LangVersion>\n    <Nullable>annotations</Nullable>\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n  </PropertyGroup>\n\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|AnyCPU'\">\n    <NoWarn>1701;1702;1705;1591;NU1701;CS8618;CS8603;CS8600;CS8604;CS8602;CS8618</NoWarn>\n\n  </PropertyGroup>\n\n  <ItemGroup>\n    <FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"HueApi.ColorConverters\" Version=\"3.0.0\" />\n    <PackageReference Include=\"LifxCloud\" Version=\"1.1.19\" />\n    <PackageReference Include=\"MediatR\" Version=\"13.1.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Abstractions\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Json\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Abstractions\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Options.ConfigurationExtensions\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.DependencyInjection\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.DependencyInjection.Abstractions\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Graph\" Version=\"5.97.0\" />\n    <PackageReference Include=\"Microsoft.Identity.Client\" Version=\"4.79.2\" />\n    <PackageReference Include=\"Microsoft.Identity.Client.Extensions.Msal\" Version=\"4.79.2\" />\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.4\" />\n    <PackageReference Include=\"OpenWiz\" Version=\"1.1.0\" />\n    <PackageReference Include=\"Polly\" Version=\"8.6.4\" />\n    <PackageReference Include=\"HueApi\" Version=\"3.0.0\" />\n    <PackageReference Include=\"Q42.HueApi\" Version=\"3.23.2\" />\n    <PackageReference Include=\"Q42.HueApi.ColorConverters\" Version=\"3.23.2\" />\n    <PackageReference Include=\"Serilog\" Version=\"4.3.0\" />\n    <PackageReference Include=\"Serilog.Extensions.Hosting\" Version=\"9.0.0\" />\n    <PackageReference Include=\"Serilog.Formatting.Compact\" Version=\"3.0.0\" />\n    <PackageReference Include=\"Serilog.Settings.Configuration\" Version=\"9.0.0\" />\n    <PackageReference Include=\"Serilog.Sinks.ApplicationInsights\" Version=\"4.1.0\" />\n    <PackageReference Include=\"Serilog.Sinks.Console\" Version=\"6.1.1\" />\n    <PackageReference Include=\"Serilog.Sinks.File\" Version=\"7.0.0\" />\n    <PackageReference Include=\"System.IO.Ports\" Version=\"10.0.0\" />\n    <PackageReference Include=\"System.Security.Cryptography.ProtectedData\" Version=\"10.0.0\" />\n    <PackageReference Include=\"System.Text.Json\" Version=\"10.0.0\" />\n    <PackageReference Include=\"YeelightAPI\" Version=\"1.10.2\" />\n  </ItemGroup>\n\n\n</Project>\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Layout/MainLayout.razor",
    "content": "﻿@inherits LayoutComponentBase\n\n<MudThemeProvider />\n<MudPopoverProvider />\n<MudDialogProvider />\n<MudSnackbarProvider />\n\n<MudLayout>\n    <MudAppBar Elevation=\"0\">\n        <MudIconButton Icon=\"@Icons.Material.Filled.Menu\" Color=\"MudBlazor.Color.Inherit\" Edge=\"Edge.Start\" OnClick=\"@((e) => DrawerToggle())\" />\n        <MudSpacer />\n        <LoginDisplay />\n    </MudAppBar>\n    <MudDrawer @bind-Open=\"_drawerOpen\" Elevation=\"1\">\n        <NavMenu />\n    </MudDrawer>\n    <MudMainContent>\n        <MudContainer MaxWidth=\"MaxWidth.Large\" Class=\"my-16\">\n            @Body\n        </MudContainer>\n    </MudMainContent>\n</MudLayout>\n\n@code {\n    bool _drawerOpen = true;\n\n    void DrawerToggle()\n    {\n        _drawerOpen = !_drawerOpen;\n    }\n}"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Layout/MainLayout.razor.css",
    "content": ".page {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n}\n\nmain {\n    flex: 1;\n}\n\n.sidebar {\n    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);\n}\n\n.top-row {\n    background-color: #f7f7f7;\n    border-bottom: 1px solid #d6d5d5;\n    justify-content: flex-end;\n    height: 3.5rem;\n    display: flex;\n    align-items: center;\n}\n\n    .top-row ::deep a, .top-row .btn-link {\n        white-space: nowrap;\n        margin-left: 1.5rem;\n    }\n\n    .top-row a:first-child {\n        overflow: hidden;\n        text-overflow: ellipsis;\n    }\n\n@media (max-width: 640.98px) {\n    .top-row:not(.auth) {\n        display: none;\n    }\n\n    .top-row.auth {\n        justify-content: space-between;\n    }\n\n    .top-row a, .top-row .btn-link {\n        margin-left: 0;\n    }\n}\n\n@media (min-width: 641px) {\n    .page {\n        flex-direction: row;\n    }\n\n    .sidebar {\n        width: 250px;\n        height: 100vh;\n        position: sticky;\n        top: 0;\n    }\n\n    .top-row {\n        position: sticky;\n        top: 0;\n        z-index: 1;\n    }\n\n    .top-row, article {\n        padding-left: 2rem !important;\n        padding-right: 1.5rem !important;\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Layout/NavMenu.razor",
    "content": "﻿<MudNavMenu>\n    <MudNavLink Href=\"\" Match=\"NavLinkMatch.All\" Icon=\"@Icons.Material.Filled.Person\">Teams Status</MudNavLink>\n    <MudNavLink Href=\"/color\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.ColorLens\">Set Light Colors</MudNavLink>\n    <MudNavGroup Title=\"Configure Lights\" Expanded=\"true\">\n        <MudNavLink Href=\"/hue\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.Lightbulb\">Philips Hue</MudNavLink>\n        <MudNavLink Href=\"/lifx\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.Lightbulb\">LIFX</MudNavLink>\n        <MudNavLink Href=\"/yeelight\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.Lightbulb\">Yeelight</MudNavLink>\n        <MudNavLink Href=\"/wiz\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.Lightbulb\">Wiz</MudNavLink>\n        <MudNavLink Href=\"/custom\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.Lightbulb\">Custom API</MudNavLink>\n        <MudNavLink Href=\"/serial\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.Lightbulb\">Local Serial Host</MudNavLink>\n    </MudNavGroup>\n    <MudNavLink Href=\"/settings\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.Settings\">Settings</MudNavLink>\n    <MudNavLink Href=\"/logs\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.FileOpen\">Logs</MudNavLink>\n    <MudNavLink Href=\"/about\" Match=\"NavLinkMatch.Prefix\" Icon=\"@Icons.Material.Filled.Info\">About</MudNavLink>\n</MudNavMenu>\n\n@code {\n    private bool collapseNavMenu = true;\n    private bool expandSubMenu;//add\n    private string expandClass = \"oi oi-caret-bottom\";\n    private string NavMenuCssClass => collapseNavMenu ? \"collapse\" : null;\n\n    private void ToggleNavMenu()\n    {\n        collapseNavMenu = !collapseNavMenu;\n    }\n\n    private void ToggleExpand()\n    {\n        expandSubMenu = !expandSubMenu;\n\n        if (expandSubMenu)\n        {\n            expandClass = \"oi oi-caret-top\";\n        }\n        else\n        {\n            expandClass = \"oi oi-caret-bottom\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Layout/NavMenu.razor.css",
    "content": ".navbar-toggler {\n    background-color: rgba(255, 255, 255, 0.1);\n}\n\n.top-row {\n    height: 3.5rem;\n    background-color: rgba(0,0,0,0.4);\n}\n\n.navbar-brand {\n    font-size: 1.1rem;\n}\n\n.oi {\n    width: 2rem;\n    font-size: 1.1rem;\n    vertical-align: text-top;\n    top: -2px;\n}\n\n.nav-item {\n    font-size: 0.9rem;\n    padding-bottom: 0.5rem;\n}\n\n    .nav-item:first-of-type {\n        padding-top: 1rem;\n    }\n\n    .nav-item:last-of-type {\n        padding-bottom: 1rem;\n    }\n\n    .nav-item ::deep a {\n        color: #d7d7d7;\n        border-radius: 4px;\n        height: 3rem;\n        display: flex;\n        align-items: center;\n        line-height: 3rem;\n    }\n\n.nav-item ::deep a.active {\n    background-color: rgba(255,255,255,0.25);\n    color: white;\n}\n\n.nav-item ::deep a:hover {\n    background-color: rgba(255,255,255,0.1);\n    color: white;\n}\n\n@media (min-width: 641px) {\n    .navbar-toggler {\n        display: none;\n    }\n\n    .collapse {\n        /* Never collapse the sidebar for wide screens */\n        display: block;\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/About.razor",
    "content": "﻿@page \"/about\"\n@inject IJSRuntime js\n@inject AppInfo _appInfo\n\n\n<MudPaper Height=\"500px\" Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.ExtraLarge\" Style=\"text-align:center\">\n        <MudImage Src=\"/_content/PresenceLight.Razor/images/profileimage.jpg\" Style=\"height:300px; width:300px\"></MudImage>\n        <MudText Typo=\"Typo.h3\">PresenceLight by Isaac Levin</MudText>\n        <MudSpacer />\n\n\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.FlexStart\" Style=\"word-wrap:break-word\">\n            <MudItem xs=\"4\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">Application Type</MudText>\n            </MudItem>\n            <MudItem xs=\"8\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">@appType</MudText>\n            </MudItem>\n            <MudItem xs=\"4\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">Application Version</MudText>\n            </MudItem>\n            <MudItem xs=\"8\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">@assemblyVersion</MudText>\n            </MudItem>\n            <MudItem xs=\"4\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">Install Location</MudText>\n            </MudItem>\n            <MudItem xs=\"8\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">@installLocation</MudText>\n            </MudItem>\n            <MudItem xs=\"4\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">Install Date</MudText>\n            </MudItem>\n            <MudItem xs=\"8\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">@installedDate</MudText>\n            </MudItem>\n            <MudItem xs=\"4\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">Runtime Version</MudText>\n            </MudItem>\n            <MudItem xs=\"8\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">@RuntimeVersionInfo</MudText>\n            </MudItem>\n            <MudItem xs=\"4\">\n                <MudText Typo=\"Typo.h6\" Align=\"Align.Left\">Settings Path</MudText>\n            </MudItem>\n            <MudItem xs=\"8\">\n                <MudLink Typo=\"Typo.h6\" Href=\"\" title=\"Click to download configuration file\" @onclick=\"@((ev) => DownloadSettings(@localConfigurationPath))\" Align=\"Align.Left\">@localConfigurationPath</MudLink>\n            </MudItem>\n        </MudGrid>\n        <br />\n        <br />\n        <h6>Found an issue or want a feature? File it <a href=\"https://github.com/isaacrlevin/PresenceLight/issues/new/choose\" target=\"_blank\">here</a></h6>\n    </MudContainer>\n</MudPaper>\n\n@code {\n    string installLocation;\n    string installedDate;\n    string RuntimeVersionInfo;\n    string assemblyVersion;\n    string localConfigurationPath;\n    string appType;\n\n    protected override void OnInitialized()\n    {\n        appType = _appInfo.GetAppInstallType();\n        installLocation = AppInfo.GetInstallLocation();\n        installedDate = AppInfo.GetInstallationDate();\n        RuntimeVersionInfo = AppInfo.GetDotNetRuntimeInfo();\n        assemblyVersion = _appInfo.GetApplicationVersion();\n        localConfigurationPath = new System.IO.FileInfo(SettingsService.GetSettingsFileLocation()).FullName;\n    }\n\n    async Task DownloadSettings(string filename)\n    {\n        string fileContents;\n        using (var fs = new System.IO.FileStream(\n                filename,\n                System.IO.FileMode.Open,\n                System.IO.FileAccess.Read,\n                System.IO.FileShare.ReadWrite))\n        {\n            var b = new byte[fs.Length];\n\n            fs.Read(b, 0, b.Length);\n            fileContents = Convert.ToBase64String(b);\n        }\n\n        await js.InvokeAsync<object>(\n                   \"saveAsFile\",\n                   filename,\n                   fileContents);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Color.razor",
    "content": "﻿@page \"/color\"\n\n@using LifxCloud.NET.Models\n@inject ILogger<Color> _logger;\n\n<MudPaper Height=\"500px\" Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.Small\" Style=\"text-align:center\">\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n            <MudItem xs=\"12\">\n                <MudText Typo=\"Typo.h3\">Set Light Color</MudText>\n            </MudItem>\n            <MudItem xs=\"12\">\n                <MudColorPicker PickerVariant=\"PickerVariant.Static\" Label=\"Basic Color Picker\" @bind-Text=\"color\" Style=\"@($\"color: {color}\")\" Placeholder=\"Select Color\" />\n            </MudItem>\n            <MudItem xs=\"6\">\n                <MudButton OnClick=\"SetColor\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\" Style=\"width:75%\">Set Color</MudButton>\n            </MudItem>\n            <MudItem xs=\"6\">\n                <MudButton OnClick=\"SyncTeamsPresence\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\" Style=\"width:75%\">Sync Teams Presence</MudButton>\n            </MudItem>\n        </MudGrid>\n    </MudContainer>\n</MudPaper>\n\n@code {\n    string color = \"#FFFFFF\";\n\n\n    async Task SetColor()\n    {\n        try\n        {\n            appState.SetLightMode(\"Custom\");\n            _logger.LogInformation(\"Light Mode: Custom\");\n\n            if (appState.LightMode == \"Custom\")\n            {\n                appState.SetCustomColor(color);\n                _logger.LogInformation($\"Custom Color: {color}\");\n            }\n\n            if (appState.LightMode == \"Custom\")\n            {\n                if (appState.Config.LightSettings.Hue.IsEnabled)\n                {\n                    if (Helpers.AreStringsNotEmpty(new string[] {appState.Config.LightSettings.Hue.HueApiKey,\n                                                    appState.Config.LightSettings.Hue.SelectedItemId }))\n                    {\n                        if (appState.Config.LightSettings.Hue.UseRemoteApi)\n                        {\n                            if (!string.IsNullOrEmpty(appState.Config.LightSettings.Hue.RemoteBridgeId))\n                            {\n                                await _mediator.Send(new Core.RemoteHueServices.SetColorCommand\n                                    {\n                                        Availability = appState.CustomColor,\n                                        Activity = appState.CustomColor,\n                                        LightId = appState.Config.LightSettings.Hue.SelectedItemId,\n                                        BridgeId = appState.Config.LightSettings.Hue.RemoteBridgeId\n                                    });\n                            }\n                        }\n                        else\n                        {\n                            if (!string.IsNullOrEmpty(appState.Config.LightSettings.Hue.HueIpAddress))\n                            {\n                                await _mediator.Send(new Core.HueServices.SetColorCommand() { Activity = appState.CustomColor, Availability = appState.CustomColor, LightID = appState.Config.LightSettings.Hue.SelectedItemId });\n                            }\n                        }\n                    }\n                }\n\n                if (appState.Config.LightSettings.LIFX.IsEnabled && !string.IsNullOrEmpty(appState.Config.LightSettings.LIFX.LIFXApiKey))\n                {\n                    await _mediator.Send(new Core.LifxServices.SetColorCommand() { Availability = appState.CustomColor, Activity = appState.CustomColor, LightId = appState.Config.LightSettings.LIFX.SelectedItemId });\n                }\n\n                if (appState.Config.LightSettings.Yeelight.IsEnabled && !string.IsNullOrEmpty(appState.Config.LightSettings.Yeelight.SelectedItemId))\n                {\n                    await _mediator.Send(new PresenceLight.Core.YeelightServices.SetColorCommand { Activity = appState.CustomColor, Availability = appState.CustomColor, LightId = appState.Config.LightSettings.Yeelight.SelectedItemId });\n                }\n\n                if (appState.Config.LightSettings.CustomApi.IsEnabled)\n                {\n                    string response = await _mediator.Send(new Core.CustomApiServices.SetColorCommand\n                        {\n                            Activity = appState.CustomColor,\n                            Availability = appState.CustomColor\n                        });\n                }\n\n                if (appState.Config.LightSettings.LocalSerialHost.IsEnabled)\n                {\n                    string response = await _mediator.Send(new Core.LocalSerialHostServices.SetColorCommand\n                        {\n                            Activity = appState.CustomColor,\n                            Availability = appState.CustomColor\n                        });\n                }\n\n                if (appState.Config.LightSettings.Wiz.IsEnabled)\n                {\n                    await _mediator.Send(new Core.WizServices.SetColorCommand\n                        {\n                            Activity = appState.CustomColor,\n                            Availability = appState.CustomColor,\n                            LightID = appState.Config.LightSettings.Wiz.SelectedItemId\n                        });\n                }\n            }\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, $\"Error Occurred Setting Custom Color {color}\");\n            throw;\n        }\n    }\n\n    void SyncTeamsPresence()\n    {\n        appState.SetLightMode(\"Graph\");\n        _logger.LogInformation(\"Light Mode: Graph\");\n    }\n\n    protected override void OnInitialized()\n    {\n        appState.OnChange += RaiseStateHasChanged;\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Color.razor.css",
    "content": ""
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/CustomApiSetup.razor",
    "content": "﻿@page \"/custom\"\n\n@using PresenceLight.Core.Initialize;\n@inject ILogger<CustomApiSetup> _logger;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.ExtraExtraLarge\" Style=\"text-align:center\">\n        <MudText Typo=\"Typo.h3\">Configure Custom API</MudText>\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n            <MudItem xs=\"12\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.CustomApi.IsEnabled\" Label=\"Connect to Custom API\"></MudCheckBox>\n            </MudItem>\n        </MudGrid>\n        @if (appState.Config.LightSettings.CustomApi.IsEnabled)\n        {\n            @foreach (var customApiSetting in appState.Config.LightSettings.CustomApi.GetType().GetProperties())\n            {\n                @if (customApiSetting.PropertyType.Name == \"CustomApiSetting\")\n                {\n                    object customApiSettingValue = customApiSetting.GetValue(appState.Config.LightSettings.CustomApi, null);\n                    var lab = $\"{customApiSetting.Name}Uri\";\n                    var  body = $\"{customApiSetting.Name}body\";\n\n                    <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                        <MudItem xs=\"12\">\n                            <strong><label for=\"@lab\">@Helpers.HumanifyText(customApiSetting.Name.Replace(\"CustomApi\",\"\")):</label></strong>\n                        </MudItem>\n                        @if (customApiSettingValue != null)\n                        {\n                            @foreach (var setting in customApiSettingValue.GetType().GetProperties())\n                            {\n                                object settingValue = setting.GetValue(customApiSettingValue, null);\n\n                                if (setting.Name == \"Method\")\n                                {\n                                    <MudItem xs=\"2\">\n                                        <label>Method</label>\n                                        <select class=\"form-control\" value=\"@settingValue?.ToString()\" @onchange=\"@((ChangeEventArgs e) => OnChange(e, setting, customApiSettingValue))\">\n                                            <option value=\"\">Select</option>\n                                            <option value=\"GET\">GET</option>\n                                            <option value=\"POST\">POST</option>\n                                        </select>\n                                    </MudItem>                                    \n                                }\n                                else if (setting.Name == \"Uri\")\n                                {                           \n                                    <MudItem xs=\"4\">\n                                        <Label>Uri</Label>\n                                        <input type=\"text\" id=\"@lab\" value=\"@settingValue?.ToString()\" @onchange=\"@((ChangeEventArgs e) => OnChange(e, setting, customApiSettingValue))\" class=\"form-control\" />\n                                    </MudItem>\n                                }\n                                else if (setting.Name == \"Body\")\n                                {\n                                    <MudItem xs=\"6\">\n                                        <Label>Body</Label>\n                                        <input type=\"text\" id=\"@body\" value=\"@settingValue?.ToString()\" @onchange=\"@((ChangeEventArgs e) => OnChange(e, setting, customApiSettingValue))\" class=\"form-control\" />\n                                    </MudItem>\n                                }\n                            }\n                        }\n                    </MudGrid>\n                }\n            }\n\n            <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                <MudItem xs=\"12\">\n                    <input type=\"checkbox\" checked=\"@appState.Config.LightSettings.CustomApi.UseBasicAuth\" @bind-value=\"@appState.Config.LightSettings.CustomApi.UseBasicAuth\" /> Use Basic Auth\n                </MudItem>\n            </MudGrid>\n\n\n            @if (appState.Config.LightSettings.CustomApi.UseBasicAuth)\n            {\n                <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                    <MudItem xs=\"3\">\n                        Basic Auth Username:\n                    </MudItem>\n                    <MudItem xs=\"7\">\n                        <input style=\"width: 100%\" @bind-value=\"@appState.Config.LightSettings.CustomApi.BasicAuthUserName\" type=\"text\" class=\"form-control\">\n                    </MudItem>\n                </MudGrid>\n\n                <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                    <MudItem xs=\"3\">\n                        Basic Auth Password:\n                    </MudItem>\n                    <MudItem xs=\"7\">\n                        <input style=\"width: 100%\" @bind-value=\"@appState.Config.LightSettings.CustomApi.BasicAuthUserPassword\" type=\"password\" class=\"form-control\">\n                    </MudItem>\n                </MudGrid>\n            }\n            <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                <MudItem xs=\"12\">\n                    <input type=\"checkbox\" checked=\"@appState.Config.LightSettings.CustomApi.IgnoreCertificateErrors\" @bind-value=\"@appState.Config.LightSettings.CustomApi.IgnoreCertificateErrors\" /> Ignore Certificate Errors\n                </MudItem>\n            </MudGrid>\n        }\n\n        <MudItem Class=\"mt-5\" xs=\"12\">\n            <MudButton Class=\"mb-10\" OnClick=\"Save\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Save</MudButton>\n            @if (settingsSaved)\n            {\n                <MudText Color=\"MudBlazor.Color.Success\">@message</MudText>\n            }\n        </MudItem>\n    </MudContainer>\n</MudPaper>\n\n\n\n@code {\n\n    bool settingsSaved = false;\n    string message;\n\n    protected override async Task OnInitializedAsync()\n    {\n        if (!appState.SignedIn)\n        {\n            NavManager.NavigateTo(\"/\");\n        }\n        try\n        {\n\n            appState.OnChange += RaiseStateHasChanged;\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred loading Custom API Page\");\n            throw;\n        }\n        await Task.CompletedTask;\n    }\n\n    private void Save()\n    {\n        try\n        {\n            SettingsService.SaveSettings(appState.Config);\n            // Check if non of the objects from appState.Config.LightSettings.CustomApi has a property method set without also having the property uri set.\n            foreach (var customApiSetting in appState.Config.LightSettings.CustomApi.GetType().GetProperties())\n            {\n                if (customApiSetting.PropertyType.Name == \"CustomApiSetting\")\n                {\n                    object customApiSettingValue = customApiSetting.GetValue(appState.Config.LightSettings.CustomApi, null);\n                    foreach (var setting in customApiSettingValue.GetType().GetProperties())\n                    {\n                        if (setting.Name == \"Method\")\n                        {\n                            object settingValue = setting.GetValue(customApiSettingValue, null);\n                            if (settingValue != null && settingValue.ToString() != \"\" && customApiSettingValue.GetType().GetProperty(\"Uri\").GetValue(customApiSettingValue, null).ToString() == \"\")\n                            {\n                                _logger.LogError(\"Uri is required when Method is set\");\n                                throw new Exception(\"Uri is required when Method is set\");\n                            }\n                        }\n                        // Check if the Uri is set without the Method being set.\n                        if (setting.Name == \"Uri\")\n                        {\n                            object settingValue = setting.GetValue(customApiSettingValue, null);\n                            if (settingValue != null && settingValue.ToString() != \"\" && customApiSettingValue.GetType().GetProperty(\"Method\").GetValue(customApiSettingValue, null).ToString() == \"\")\n                            {\n                                _logger.LogError(\"Method is required when Uri is set\");\n                                throw new Exception(\"Method is required when Uri is set\");\n                            }\n                        }\n                    }\n                }\n            }\n            _mediator.Send(new InitializeCommand { AppState = appState });\n\n            message = \"Settings Saved\";\n            settingsSaved = true;\n            _logger.LogInformation(\"Settings Saved from Custom API Page\");\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred Saving Custom API Settings\");\n            throw;\n        }\n    }\n\n    private void OnChange(ChangeEventArgs e, object setting, object customApiSettingValue)\n    {\n        var newSetting = e.Value;\n        ((PropertyInfo)setting).SetValue(customApiSettingValue, newSetting);\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/HueSetup.razor",
    "content": "﻿@page \"/hue\"\n\n@inject ILogger<HueSetup> _logger;\n@inject IDialogService DialogService;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.ExtraExtraLarge\" Style=\"text-align:center\">\n        <MudText Typo=\"Typo.h3\">Configure Phlips Hue</MudText>\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n            <MudItem xs=\"4\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.Hue.IsEnabled\" Label=\"Connect to Philips Hue\"></MudCheckBox>\n            </MudItem>\n            <MudItem xs=\"4\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.Hue.UseRemoteApi\" Label=\"Use Remote API\"></MudCheckBox>\n            </MudItem>\n            <MudItem xs=\"4\">\n                <MudButton Disabled=\"@(!appState.Config.LightSettings.Hue.UseRemoteApi)\" OnClick=\"LoginRemoteApi\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Login to Hue Cloud</MudButton>\n            </MudItem>\n        </MudGrid>\n        @if (appState.Config.LightSettings.Hue.IsEnabled)\n        {\n            @if (!appState.Config.LightSettings.Hue.UseRemoteApi)\n            {\n                <MudGrid Class=\"mt-5\" Justify=\" Justify.Center\">\n                    <MudItem xs=\"       12\">\n                        <MudButton OnClick=\"        FindBridge\" Variant=\"    Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Find Hue Bridge</MudButton>\n                    </MudItem>\n                    <MudItem xs=\"4\">\n                        <EditForm Model=\"@appState.Config\" OnValidSubmit=\" RegisterBridge\">\n                            <MudTextField Variant=\"       Variant.Outlined\" @bind-Value=\"@appState.Config.LightSettings.Hue.HueIpAddress\" />\n                            <br />\n                            <DataAnnotationsValidator />\n                            <Microsoft.AspNetCore.Components.Forms.ValidationSummary />\n                            <br />\n                            <br />\n                            <MudButton ButtonType=\"   MudBlazor.ButtonType.Submit\" Variant=\"Variant.Filled\" Color=\"     MudBlazor.Color.Primary\">Register Bridge</MudButton>\n                        </EditForm>\n                    </MudItem>\n                </MudGrid>\n            }\n            @if (showHueMessage)\n            {\n                <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                    <MudItem xs=\"12\">\n                        <MudText Class=@hueMessageClass Typo=\"Typo.body1\">@hueMessage</MudText>\n                    </MudItem>\n                </MudGrid>\n            }\n            <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                <MudItem xs=\"12\">\n                    <MudButton @onclick=\"@((ev) => CheckHue(\"Lights\"))\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Get Hue Lights</MudButton>\n                    <span>&nbsp;&nbsp;</span>\n                    <MudButton @onclick=\"@((ev) => CheckHue(\"Groups\"))\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Get Hue Groups</MudButton>\n                </MudItem>\n            </MudGrid>\n            if (isLoadingLights)\n            {\n                <br />\n\n                <br />\n                <Circle Center=\"true\" />\n            }\n            else\n            {\n                @if (appState.HueLights != null)\n                {\n\n                    <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                        <MudItem xs=\"4\">\n                            <MudSelect Value=@($\"{selectedLightLabel}\") Dense=\"true\" Label=\"@($\"Select Hue {lastType}\")\" T=\"string\" Variant=\"Variant.Outlined\" ValueChanged=\"OnChange\">\n                                @foreach (var light in appState.HueLights)\n                                {\n                                    if (light.GetType() == typeof(HueApi.Models.GroupedLight))\n                                    {\n                                        var obj = (HueApi.Models.GroupedLight)light;\n                                        <MudSelectItem Value=\"@($\"group_id:{@obj.IdV1}\")\">@obj.Metadata?.Name</MudSelectItem>\n                                    }\n                                    else\n                                    {\n                                        var obj = (HueApi.Models.Light)light;\n\t\t\t\t\t\t\t\t\t\t<MudSelectItem Value=\"@($\"id:{@obj.IdV1}\")\">@obj.Metadata?.Name</MudSelectItem>\n                                    }\n\n                                }\n                            </MudSelect>\n                        </MudItem>\n                    </MudGrid>\n\n                    <MudGrid Class=\"mt-5 align-center\" Justify=\"Justify.FlexStart\">\n                        <MudItem xs=\"3\">\n                            <MudText Typo=\"Typo.h5\">Brightness</MudText>\n                        </MudItem>\n                        <MudItem xs=\"6\">\n                            <MudSlider @bind-Value=\"appState.Config.LightSettings.Hue.Brightness\" Min=\"0\" Max=\"100\"></MudSlider>\n                        </MudItem>\n                        <MudItem xs=\"2\">\n                            <MudNumericField @bind-Value=\"appState.Config.LightSettings.Hue.Brightness\" Variant=\"Variant.Outlined\" Min=\"0\" Max=\"100\" Step=\"1\" />\n                        </MudItem>\n                    </MudGrid>\n\n                    <Statuses Light=\"@appState.Config.LightSettings.Hue\"></Statuses>\n                }\n            }\n        }\n        <MudItem Class=\"mt-5\" xs=\"12\">\n            <MudButton Class=\"mb-10\" OnClick=\"Save\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Save</MudButton>\n            @if (settingsSaved)\n            {\n                <MudText Color=\"MudBlazor.Color.Success\">@message</MudText>\n            }\n        </MudItem>\n    </MudContainer>\n</MudPaper>\n\n@code {\n\n    bool settingsSaved = false;\n    bool isLoadingLights = false;\n    string message;\n    string hueMessageClass;\n\n    bool showHueMessage = false;\n    string hueMessage;\n    string selectedLightLabel = \"\";\n    string lastType = \"\";\n\n    protected override async Task OnInitializedAsync()\n    {\n        try\n        {\n            if (!appState.SignedIn)\n            {\n                NavManager.NavigateTo(\"/\");\n            }\n\n            await CheckHue();\n\n            appState.OnChange += RaiseStateHasChanged;\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred loading Hue Page\");\n            throw;\n        }\n    }\n\n\n    private async Task Save()\n    {\n        try\n        {\n            await SettingsService.SaveSettings(appState.Config);\n\n            _mediator.Send(new Core.HueServices.InitializeCommand() { AppState = appState }).Wait();\n\n            message = \"Settings Saved\";\n            settingsSaved = true;\n            _logger.LogInformation(\"Settings Saved from Hue Page\");\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred Saving Hue Settings\");\n            throw;\n        }\n    }\n\n    private async Task LoginRemoteApi()\n    {\n        if (appState.Config.LightSettings.Hue.UseRemoteApi)\n        {\n            if (!appState.Config.LightSettings.Hue.IsEnabled)\n            {\n                appState.Config.LightSettings.Hue.IsEnabled = true;\n            }\n\n            try\n            {\n                _logger.LogInformation(\"Cloud Hue Login Initialized\");\n                var (bridgeId, apiKey, bridgeIp) = await _mediator.Send(new PresenceLight.Core.RemoteHueServices.RegisterBridgeCommand());\n                if (Helpers.AreStringsNotEmpty(new string[] { apiKey, bridgeId }))\n                {\n                    appState.Config.LightSettings.Hue.HueApiKey = apiKey;\n                    appState.Config.LightSettings.Hue.RemoteBridgeId = bridgeId;\n                    appState.Config.LightSettings.Hue.HueIpAddress = bridgeIp;\n                    _logger.LogInformation(\"Cloud Hue Login Successful\");\n                    await Save();\n                }\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error Occurred Getting Cloud Hue Api Key\");\n            }\n        }\n    }\n\n    async Task FindBridge()\n    {\n        try\n        {\n            _logger.LogInformation(\"Hue Bridge Lookup Initialized\");\n            appState.Config.LightSettings.Hue.HueIpAddress = await _mediator.Send(new PresenceLight.Core.HueServices.FindBridgeCommand());\n            _logger.LogInformation(\"Hue Bridge Lookup Successful\");\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Error Occurred Getting Finding Hue Bridge\");\n        }\n    }\n\n\n    async Task RegisterBridge()\n    {\n        _logger.LogInformation(\"Hue Bridge Registration Initialized\");\n\n\n        var options = new DialogOptions { CloseOnEscapeKey = true };\n        var dialog = DialogService.Show<Confirm>(\"Please press the sync button on your Philips Hue Bridge\", options);\n\n        var result = await dialog.Result;\n\n        if (result.Canceled)\n        {\n            _logger.LogInformation(\"Hue Bridge Registration Cancelled\");\n        }\n        else\n        {\n            try\n            {\n                appState.Config.LightSettings.Hue.HueApiKey = await _mediator.Send(new Core.HueServices.RegisterBridgeCommand());\n                _logger.LogInformation(\"Hue Bridge Registration Successful\");\n                await Save();\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error Occurred Registering Hue Bridge\");\n                hueMessage = \"Error Occurred registering bridge, please try again\";\n                showHueMessage = true;\n                hueMessageClass = \"text-danger\";\n            }\n\n            if (!string.IsNullOrEmpty(appState.Config.LightSettings.Hue.HueApiKey))\n            {\n                try\n                {\n                    showHueMessage = true;\n                    hueMessage = \"App Registered with Bridge\";\n                    hueMessageClass = \"text-success\";\n\n\n                    appState.SetHueLights(await _mediator.Send(new PresenceLight.Core.HueServices.GetLightsCommand()));\n\n                    if (string.IsNullOrEmpty(appState.Config.LightSettings.Hue.SelectedItemId) && appState.HueLights.Count() > 0)\n                    {\n                        var firstLight = appState.HueLights.FirstOrDefault();\n                        if (firstLight is HueApi.Models.Light light)\n                        {\n\t\t\t\t\t\t\tappState.Config.LightSettings.Hue.SelectedItemId = $\"id:{light.IdV1}\";\n                        }\n                    }\n                }\n                catch (Exception e)\n                {\n                    _logger.LogError(e, \"Error Occurred Getting Hue Lights\");\n                    throw;\n                }\n\n                await Save();\n            }\n\n            else\n            {\n                _logger.LogError(\"Hue Api Key not configured\");\n                hueMessage = \"Api Key Not Created, please try again and ensure you press the sync button on your bridge\";\n                showHueMessage = true;\n                hueMessageClass = \"text-danger\";\n            }\n        }\n    }\n\n    async Task CheckHue(string type = null)\n    {\n        if (appState.Config.LightSettings.Hue.IsEnabled)\n        {\n            isLoadingLights = true;\n            if (string.IsNullOrEmpty(lastType) || type != lastType)\n            {\n                if (string.IsNullOrEmpty(type))\n                {\n                    if (!string.IsNullOrEmpty(appState.Config.LightSettings.Hue.SelectedItemId))\n                    {\n                        if (appState.Config.LightSettings.Hue.SelectedItemId.Contains(\"group\"))\n                        {\n                            type = \"Groups\";\n                        }\n                        else\n                        {\n                            type = \"Lights\";\n                        }\n                    }\n                    else\n                    {\n                        appState.Config.LightSettings.Hue.SelectedItemId = string.Empty;\n                        type = \"Lights\";\n                    }\n                }\n                else\n                {\n                    appState.Config.LightSettings.Hue.SelectedItemId = string.Empty;\n                    selectedLightLabel = string.Empty;\n                }\n                lastType = type;\n            }\n\n            _logger.LogInformation($\"Get Hue {type} Initialized\");\n\n            if (!string.IsNullOrEmpty(appState.Config.LightSettings.Hue.HueApiKey))\n            {\n                try\n                {\n                    if (type == \"Groups\")\n                    {\n                        if (appState.Config.LightSettings.Hue.UseRemoteApi)\n                        {\n                            appState.SetHueLights(await _mediator.Send(new Core.RemoteHueServices.GetGroupsCommand()));\n                        }\n                        else\n                        {\n                            appState.SetHueLights(await _mediator.Send(new Core.HueServices.GetGroupsCommand()));\n                        }\n\n\n                        if (string.IsNullOrEmpty(appState.Config.LightSettings.Hue.SelectedItemId) && appState.HueLights.Count() > 0)\n                        {\n                            var firstGroup = appState.HueLights.FirstOrDefault();\n                            if (firstGroup is HueApi.Models.GroupedLight group)\n                            {\n\t\t\t\t\t\t\t\tappState.Config.LightSettings.Hue.SelectedItemId = $\"group_id:{group.IdV1}\";\n                            }\n                        }\n                        else\n                        {\n                            var matchingGroup = appState.HueLights.OfType<HueApi.Models.GroupedLight>()\n\t\t\t\t\t\t\t\t.Where(a => a.IdV1 == appState.Config.LightSettings.Hue.SelectedItemId.Replace(\"group_id:\", \"\"))\n                                .FirstOrDefault();\n                            if (matchingGroup != null)\n                            {\n                                selectedLightLabel = matchingGroup.Metadata?.Name;\n                            }\n                        }\n                    }\n                    else\n                    {\n                        if (appState.Config.LightSettings.Hue.UseRemoteApi)\n                        {\n                            appState.SetHueLights(await _mediator.Send(new Core.RemoteHueServices.GetLightsCommand()));\n                        }\n                        else\n                        {\n                            appState.SetHueLights(await _mediator.Send(new Core.HueServices.GetLightsCommand()));\n                        }\n\n\n                        if (string.IsNullOrEmpty(appState.Config.LightSettings.Hue.SelectedItemId) && appState.HueLights.Count() > 0)\n                        {\n                            var firstLight = appState.HueLights.FirstOrDefault();\n                            if (firstLight is HueApi.Models.Light light)\n                            {\n\t\t\t\t\t\t\t\tappState.Config.LightSettings.Hue.SelectedItemId = $\"id:{light.IdV1}\";\n                            }\n                        }\n                        else\n                        {\n                            var matchingLight = appState.HueLights.OfType<HueApi.Models.Light>()\n\t\t\t\t\t\t\t\t.Where(a => a.IdV1 == appState.Config.LightSettings.Hue.SelectedItemId.Replace(\"id:\", \"\"))\n                                .FirstOrDefault();\n                            if (matchingLight != null)\n                            {\n                                selectedLightLabel = matchingLight.Metadata?.Name;\n                            }\n                        }\n                    }\n\n                    await Save();\n\n                    showHueMessage = true;\n                    hueMessage = \"Connected to Hue\";\n                    hueMessageClass = \"text-success\";\n\n                    _logger.LogInformation($\"Get Hue {type} Successful\");\n                }\n                catch (Exception e)\n                {\n                    _logger.LogError(e, \"Error Occurred Getting Hue Lights\");\n                    showHueMessage = true;\n                    hueMessage = \"Error Occurred Connecting to Hue, please try again\";\n                    hueMessageClass = \"text-danger\";\n                    this.StateHasChanged();\n                    appState.SetHueLights(new List<HueApi.Models.Light>());\n                }\n            }\n            isLoadingLights = false;\n        }\n    }\n\n    private void OnChange(string e)\n    {\n        var light = e;\n        appState.Config.LightSettings.Hue.SelectedItemId = light;\n        appState.SetHueLight(light);\n        if (lastType == \"Groups\")\n        {\n            var matchingGroup = appState.HueLights.OfType<HueApi.Models.GroupedLight>()\n\t\t\t\t.Where(a => a.IdV1 == appState.Config.LightSettings.Hue.SelectedItemId.Replace(\"group_id:\", \"\"))\n                .FirstOrDefault();\n            if (matchingGroup != null)\n            {\n                selectedLightLabel = matchingGroup.Metadata?.Name;\n            }\n        }\n        else\n        {\n            var matchingLight = appState.HueLights.OfType<HueApi.Models.Light>()\n\t\t\t\t.Where(a => a.IdV1 == appState.Config.LightSettings.Hue.SelectedItemId.Replace(\"id:\", \"\"))\n                .FirstOrDefault();\n            if (matchingLight != null)\n            {\n                selectedLightLabel = matchingLight.Metadata?.Name;\n            }\n        }        \n\n        _logger.LogInformation($\"Selected Hue Light Set: {light}\");\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Index.razor",
    "content": "﻿@page \"/\"\n\n@using Microsoft.Identity.Web\n@using Microsoft.Graph\n@inject ILogger<Index> _logger;\n@inject LoginService _loginService;\n@inject Microsoft.AspNetCore.Http.IHttpContextAccessor _httpContextAccessor\n\n@if (appState.SignedIn)\n{\n    <SpinLoader IsLoading=\"@(appState.Presence == null)\">\n        <LoadingTemplate>\n            <div style=\"height:400px; position:relative; \">\n                <Circle style=\" margin: 0; position: absolute; top: 50%; left: 50%; -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%);\"\n                        Center=\"true\" />\n            </div>\n        </LoadingTemplate>\n        <ContentTemplate>\n            <MudPaper Width=\"100%\" Elevation=\"0\">\n                <MudContainer MaxWidth=\"MaxWidth.Small\" Style=\"text-align:center\">\n                    <MudGrid Class=\"mt-5\" Justify=\"Justify.FlexStart\">\n                        <MudItem xs=\"12\">\n                            <MudText Typo=\"Typo.h3\">@appState.User?.DisplayName</MudText>\n                        </MudItem>\n                        <MudItem xs=\"12\">\n                            <MudAvatar Class=\"mt-5\"\n                                       Style=\"height:300px; width:300px\">\n                                <MudImage Src=\"@(@appState.ProfileImage != null ? @appState.ProfileImage : \"/_content/PresenceLight.Razor/images/unknownprofile.png\")\" />\n                            </MudAvatar>\n                            <div class=\"circle bottom-right @appState.Presence?.Availability\"></div>\n                        </MudItem>\n                        <MudItem Class=\"mt-n16\" xs=\"12\">\n                            <MudText Typo=\"Typo.h5\">\n                                Availability: @Helpers.HumanifyText(appState.Presence?.Availability)\n                            </MudText>\n                            <MudText Typo=\"Typo.h5\">Activity: @Helpers.HumanifyText(appState.Presence?.Activity)</MudText>\n                        </MudItem>\n                    </MudGrid>\n                </MudContainer>\n            </MudPaper>\n        </ContentTemplate>\n    </SpinLoader>\n}\nelse if (appState.AadConfigComplete)\n{\n    <MudPaper Height=\"500px\" Width=\"100%\" Elevation=\"0\">\n        <MudContainer MaxWidth=\"MaxWidth.Small\" Style=\"text-align:center\">\n            <MudText Typo=\"Typo.h6\"> Login to Microsoft Account to Sync Presence</MudText>\n            <MudButton OnClick=\"SignIn\" Variant=\"Variant.Filled\">Sign In</MudButton>\n        </MudContainer>\n    </MudPaper>\n}\nelse\n{\n    <MudPaper Height=\"500px\" Width=\"100%\" Elevation=\"0\">\n        <MudContainer MaxWidth=\"MaxWidth.Small\" Style=\"text-align:center\">\n            <MudText Typo=\"Typo.h6\"> Enter Microsoft Entra / Azure AD configuration in Settings</MudText>\n        </MudContainer>\n    </MudPaper>\n}\n\n@code {\n    string image;\n    protected override async Task OnInitializedAsync()\n    {\n        appState.OnChange += RaiseStateHasChanged;\n        bool isUserAuth = await _loginService.IsUserAuthenticated();\n        if (!isUserAuth && appState.User == null)\n        {\n            appState.SetLightMode(\"\");\n\n            if (appState.Config.AppType == \"Web\")\n            {\n                await _httpContextAccessor.HttpContext.ChallengeAsync();\n            }\n        }\n        else\n        {\n            if (appState.LightMode != \"Custom\")\n            {\n                appState.SignedIn = true;\n                image = @appState.ProfileImage != null ? @appState.ProfileImage :\n                \"/_content/PresenceLight.Razor/images/unknownprofile.png\";\n                appState.SetLightMode(\"Graph\");\n                _logger.LogInformation(\"Light Mode: Graph\");\n            }\n        }\n    }\n\n    void SignIn()\n    {\n        _logger.LogInformation(\"Sign In Requested\");\n        appState.SignInRequested = true;\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Index.razor.css",
    "content": ".Available {\n    background-color: green;\n}\n\n.AvailableIdle {\n    background-color: yellow;\n}\n\n.Away {\n    background-color: yellow;\n}\n\n.Busy {\n    background-color: red;\n}\n\n.BeRightBack {\n    background-color: yellow;\n}\n\n.DoNotDisturb {\n    background-color: darkred;\n}\n\n.Offline {\n    background-color: white;\n}\n\n.PresenceUnknown {\n    background-color: white;\n}\n\n.bottom-right {\n    position: relative;\n    bottom: 120px;\n    left: 55%;\n}\n\n.circle {\n    height: 120px;\n    width: 120px;\n    border-radius: 50%;\n    border-style: solid;\n    border-width: 5px;\n    border-color: white;\n}\n\n.image {\n    width: 300px;\n    height: 300px;\n    border-radius: 50%;\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Lifx.razor",
    "content": "﻿@page \"/lifx\"\n\n@inject ILogger<Lifx> _logger;\n@inject LIFXOAuthHelper _lIFXOAuthHelper;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.ExtraExtraLarge\" Style=\"text-align:center\">\n        <MudText Typo=\"Typo.h3\">Configure LIFX</MudText>\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n            <MudItem xs=\"12\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.LIFX.IsEnabled\" Label=\"Connect to LIFX\"></MudCheckBox>\n            </MudItem>\n        </MudGrid>\n        @if (appState.Config.LightSettings.LIFX.IsEnabled)\n        {\n            <MudGrid Class=\"mt-5 align-center\" Justify=\"Justify.FlexStart\">\n                <MudItem xs=\"3\">\n                    <h5>LIFX Token</h5>\n                </MudItem>\n                <MudItem xs=\"5\">\n                    <MudTextField Variant=\"Variant.Outlined\" Value=\"@appState.Config.LightSettings.LIFX.LIFXApiKey\"></MudTextField>\n                </MudItem>\n                <MudItem xs=\"2\">\n                    <MudButton OnClick=\"GetToken\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Get Token</MudButton>\n                </MudItem>\n            </MudGrid>\n\n            <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                <MudItem xs=\"12\">\n                    <MudButton OnClick=\"@((ev) => CheckLIFX(\"Lights\"))\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Find LIFX Lights</MudButton>\n                    <MudButton OnClick=\"@((ev) => CheckLIFX(\"Groups\"))\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Find LIFX Groups</MudButton>\n                </MudItem>\n            </MudGrid>\n            if (isLoadingLights)\n            {\n                <br />\n\n                <br />\n                <Circle Center=\"true\" />\n            }\n            else\n            {\n                @if (appState.LIFXLights != null)\n                {\n                    <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                        <MudItem xs=\"4\">\n                            <MudSelect Dense=\"true\" Value=@($\"{selectedLightLabel}\") Label=\"@($\"Select LIFX {lastType}\")\" T=\"string\" Variant=\"Variant.Outlined\" ValueChanged=\"OnChange\">\n                                @foreach (var light in appState.LIFXLights)\n                                {\n                                    if (light.GetType() == typeof(LifxCloud.NET.Models.Group))\n                                    {\n                                        var obj = (LifxCloud.NET.Models.Group)light;\n                                        <MudSelectItem Value=\"@($\"group_id:{@obj.Id}\")\">@obj.Label</MudSelectItem>\n                                    }\n                                    else\n                                    {\n                                        var obj = (LifxCloud.NET.Models.Light)light;\n                                        <MudSelectItem Value=\"@($\"id:{@obj.Id}\")\">@obj.Label</MudSelectItem>\n                                    }\n                                }\n                            </MudSelect>\n                        </MudItem>\n                    </MudGrid>\n\n                    <MudGrid Class=\"mt-5 align-center\" Justify=\"Justify.FlexStart\">\n                        <MudItem xs=\"3\">\n                            <MudText Typo=\"Typo.h5\">Brightness</MudText>\n                        </MudItem>\n                        <MudItem xs=\"6\">\n                            <MudSlider @bind-Value=\"appState.Config.LightSettings.LIFX.Brightness\" Min=\"0\" Max=\"100\"></MudSlider>\n                        </MudItem>\n                        <MudItem xs=\"2\">\n                            <MudNumericField @bind-Value=\"appState.Config.LightSettings.LIFX.Brightness\" Variant=\"Variant.Outlined\" Min=\"0\" Max=\"100\" Step=\"1\" />\n                        </MudItem>\n                    </MudGrid>\n\n                    <Statuses Light=\"@appState.Config.LightSettings.LIFX\"></Statuses>\n                }\n            }\n        }\n        <MudItem Class=\"mt-5\" xs=\"12\">\n            <MudButton Class=\"mb-10\" OnClick=\"Save\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Save</MudButton>\n            @if (settingsSaved)\n            {\n                <MudText Color=\"MudBlazor.Color.Success\">@message</MudText>\n            }\n        </MudItem>\n    </MudContainer>\n</MudPaper>\n\n@code {\n    bool settingsSaved = false;\n    string message;\n\n    bool showLifxMessage = false;\n    string lifxMessage;\n    string lastType = \"\";\n    bool isLoadingLights = false;\n    string selectedLightLabel = \"\";\n\n    protected override async Task OnInitializedAsync()\n    {\n        try\n        {\n            appState.OnChange += RaiseStateHasChanged;\n\n            if (!appState.SignedIn)\n            {\n                NavManager.NavigateTo(\"/\");\n            }\n\n            await CheckLIFX();\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred loading LIFX Setup\");\n            throw;\n        }\n    }\n\n    private async Task Save()\n    {\n        try\n        {\n            await SettingsService.SaveSettings(appState.Config);\n            message = \"Settings Saved\";\n            settingsSaved = true;\n            _logger.LogInformation(\"Settings Saved from LIFX Page\");\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred Saving Lifx Settings\");\n            throw;\n        }\n    }\n\n    async Task CheckLIFX(string type = null)\n    {\n        if (@appState.Config.LightSettings.LIFX.IsEnabled)\n        {\n            isLoadingLights = true;\n            if (string.IsNullOrEmpty(lastType) || type != lastType)\n            {\n                if (string.IsNullOrEmpty(type))\n                {\n                    if (!string.IsNullOrEmpty(appState.Config.LightSettings.LIFX.SelectedItemId))\n                    {\n                        if (appState.Config.LightSettings.LIFX.SelectedItemId.Contains(\"group\"))\n                        {\n                            type = \"Groups\";\n                        }\n                        else\n                        {\n                            type = \"Lights\";\n                        }\n                    }\n                    else\n                    {\n                        appState.Config.LightSettings.LIFX.SelectedItemId = string.Empty;\n                        selectedLightLabel = string.Empty;\n                        type = \"Lights\";\n                    }\n                }\n                else\n                {\n                    appState.Config.LightSettings.LIFX.SelectedItemId = string.Empty;\n                    selectedLightLabel = string.Empty;\n                }\n                lastType = type;\n            }\n\n            _logger.LogInformation($\"Get LIFX {type} Initialized\");\n\n            if (!string.IsNullOrEmpty(appState.Config.LightSettings.LIFX.LIFXApiKey))\n            {\n                try\n                {\n                    if (type == \"Groups\")\n                    {\n\n                        appState.SetLIFXLights(await _mediator.Send(new Core.LifxServices.GetAllGroupsCommand() { ApiKey = appState.Config.LightSettings.LIFX.LIFXApiKey }));\n\n                        if (string.IsNullOrEmpty(appState.Config.LightSettings.LIFX.SelectedItemId) && appState.LIFXLights.Count() > 0)\n                        {\n                            var obj = (LifxCloud.NET.Models.Group)appState.LIFXLights.FirstOrDefault();\n                            appState.Config.LightSettings.LIFX.SelectedItemId = $\"group_id:{obj.Id}\";\n                        }\n                        else\n                        {\n                            selectedLightLabel = ((LifxCloud.NET.Models.Group)appState.LIFXLights.Where(a => ((LifxCloud.NET.Models.Group)a).Id == appState.Config.LightSettings.LIFX.SelectedItemId.Replace(\"group_id:\", \"\")).FirstOrDefault()).Label;\n                        }\n                    }\n                    else\n                    {\n                        appState.SetLIFXLights(await _mediator.Send(new Core.LifxServices.GetAllLightsCommand() { ApiKey = appState.Config.LightSettings.LIFX.LIFXApiKey }));\n\n                        if (string.IsNullOrEmpty(appState.Config.LightSettings.LIFX.SelectedItemId) && appState.LIFXLights.Count() > 0)\n                        {\n                            var obj = (LifxCloud.NET.Models.Light)appState.LIFXLights.FirstOrDefault();\n                            appState.Config.LightSettings.LIFX.SelectedItemId = $\"id:{obj.Id}\";\n                        }\n                        else\n                        {\n                            selectedLightLabel = ((LifxCloud.NET.Models.Light)appState.LIFXLights.Where(a => ((LifxCloud.NET.Models.Light)a).Id == appState.Config.LightSettings.LIFX.SelectedItemId.Replace(\"id:\", \"\")).FirstOrDefault()).Label;\n                        }\n                    }\n\n                    await Save();\n\n                    showLifxMessage = true;\n                    lifxMessage = \"Connected to LIFX Cloud\";\n\n                    _logger.LogInformation($\"Get LIFX {type} Successful\");\n                }\n                catch (Exception e)\n                {\n                    _logger.LogError(e, \"Error Occurred Getting LIFX Lights\");\n                    showLifxMessage = true;\n                    lifxMessage = \"Error Occurred Connecting to LIFX, please try again\";\n                    this.StateHasChanged();\n                    appState.SetLIFXLights(new List<LifxCloud.NET.Models.Light>());\n                }\n            }\n            isLoadingLights = false;\n        }\n    }\n\n    async Task GetToken()\n    {\n        _logger.LogInformation(\"LIFX Token Retrieval Initialized\");\n\n        if (!@appState.Config.LightSettings.LIFX.IsEnabled)\n        {\n            appState.Config.LightSettings.LIFX.IsEnabled = true;\n        }\n        var token = await _lIFXOAuthHelper.InitiateTokenRetrieval();\n        appState.Config.LightSettings.LIFX.LIFXApiKey = token;\n        if (!@appState.Config.LightSettings.LIFX.IsEnabled)\n        {\n            appState.Config.LightSettings.LIFX.IsEnabled = true;\n        }\n\n        await Save();\n        _logger.LogInformation(\"LIFX Token Retrieveal Successful\");\n    }\n\n    private void OnChange(string e)\n    {\n        var light = e;\n        appState.Config.LightSettings.LIFX.SelectedItemId = light;\n        appState.SetLIFXLight(light);\n        _logger.LogInformation($\"Selected LIFX Light Set: {light}\");\n\n        if (e.Contains(\"group\"))\n        {\n            selectedLightLabel = ((LifxCloud.NET.Models.Group)appState.LIFXLights.Where(a => ((LifxCloud.NET.Models.Group)a).Id == appState.Config.LightSettings.LIFX.SelectedItemId.Replace(\"group_id:\", \"\")).FirstOrDefault()).Label;\n        }\n        else\n        {\n            selectedLightLabel = ((LifxCloud.NET.Models.Light)appState.LIFXLights.Where(a => ((LifxCloud.NET.Models.Light)a).Id == appState.Config.LightSettings.LIFX.SelectedItemId.Replace(\"id:\", \"\")).FirstOrDefault()).Label;\n        }\n\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/LocalSerialHostSetup.razor",
    "content": "﻿@page \"/serial\"\n\n@using PresenceLight.Core.LocalSerialHostServices;\n@inject ILogger<LocalSerialHostSetup> _logger;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.ExtraExtraLarge\" Style=\"text-align:center\">\n        <MudText Typo=\"Typo.h3\">Configure Local Serial </MudText>\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n            <MudItem xs=\"12\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.LocalSerialHost.IsEnabled\" Label=\"Connect to Local Serial Host\"></MudCheckBox>\n            </MudItem>\n        </MudGrid>\n        @if (appState.Config.LightSettings.LocalSerialHost.IsEnabled)\n        {\n            <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                <MudItem xs=\"12\">\n                    <MudButton OnClick=\"GetSerialHosts\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Get Serial Ports</MudButton>\n                </MudItem>\n            </MudGrid>\n            if (isLoadingLights)\n            {\n                <br />\n\n                <br />\n                <Circle Center=\"true\" />\n            }\n            else\n            {\n                @if (appState.LocalSerialHosts != null)\n                {\n                    @foreach (var LocalSerialHostSetting in appState.Config.LightSettings.LocalSerialHost.GetType().GetProperties())\n                    {\n                        @if (LocalSerialHostSetting.PropertyType.Name == \"LocalSerialHostSetting\")\n                        {\n                            object LocalSerialHostSettingValue = LocalSerialHostSetting.GetValue(appState.Config.LightSettings.LocalSerialHost, null);\n                            var lab = $\"{LocalSerialHostSetting.Name}Uri\";\n\n                            <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                                <MudItem xs=\"3\">\n                                    <strong><label for=\"@lab\">@Helpers.HumanifyText(LocalSerialHostSetting.Name.Replace(\"LocalSerialHost\",\"\")):</label></strong>\n                                </MudItem>\n                                @if (LocalSerialHostSettingValue != null)\n                                {\n                                    @foreach (var setting in LocalSerialHostSettingValue.GetType().GetProperties())\n                                    {\n                                        object settingValue = setting.GetValue(LocalSerialHostSettingValue, null);\n\n                                        if (setting.Name == \"Port\")\n                                        {\n                                            <MudItem xs=\"2\">\n                                                <select class=\"form-control\" value=\"@settingValue?.ToString()\" @onchange=\"@((ChangeEventArgs e) => OnChange(e, setting, LocalSerialHostSettingValue))\">\n                                                    <option value=\"\" disabled=\"disabled\">Select</option>\n                                                    @foreach(var port in appState.LocalSerialHosts)\n                                                    {\n                                                        <option value=\"@port\">@port</option>\n                                                    }\n                                                </select>\n                                            </MudItem>\n                                        }\n                                        else if (setting.Name == \"BaudRate\")\n                                        {\n                                            <MudItem xs=\"2\">\n                                                <select class=\"form-control\" value=\"@settingValue?.ToString()\" @onchange=\"@((ChangeEventArgs e) => OnChange(e, setting, LocalSerialHostSettingValue))\">\n                                                    <option value=\"\" disabled=\"disabled\">Select</option>\n                                                    <option value=\"\">None</option>\n                                                    <option value=\"300\">300</option>\n                                                    <option value=\"1200\">1200</option>\n                                                    <option value=\"2400\">2400</option>\n                                                    <option value=\"4800\">4800</option>\n                                                    <option value=\"9600\">9600</option>\n                                                    <option value=\"19200\">19200</option>\n                                                    <option value=\"38400\">38400</option>\n                                                    <option value=\"57600\">57600</option>\n                                                    <option value=\"115200\">115200</option>\n                                                </select>\n                                            </MudItem>\n                                        }\n                                        else if (setting.Name == \"LineEnding\")\n                                        {\n                                            <MudItem xs=\"2\">\n                                                <select class=\"form-control\" value=\"@settingValue?.ToString()\" @onchange=\"@((ChangeEventArgs e) => OnChange(e, setting, LocalSerialHostSettingValue))\">\n                                                    <option value=\"\" disabled=\"disabled\">Select</option>\n                                                    <option value=\"\">None</option>\n                                                    <option value=\"CR\">CR</option>\n                                                    <option value=\"LF\">LF</option>\n                                                    <option value=\"CRLF\">CRLF</option>\n                                                </select>\n                                            </MudItem>\n                                        }\n                                    }\n                                }\n                            </MudGrid>\n                        }\n                        else if (LocalSerialHostSetting.PropertyType.Name == \"String\")\n                        {\n                            @if (LocalSerialHostSetting.Name != \"SelectedItemId\")\n                            {\n                                object parent = appState.Config.LightSettings.LocalSerialHost;\n                                object LocalSerialHostSettingValue = LocalSerialHostSetting.GetValue(appState.Config.LightSettings.LocalSerialHost, null);\n                                var lab = $\"{LocalSerialHostSetting.Name}Uri\";\n\n                                <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                                    <MudItem xs=\"3\">\n                                        <strong><label for=\"@lab\">@Helpers.HumanifyText(LocalSerialHostSetting.Name.Replace(\"LocalSerialHost\",\"\")):</label></strong>\n                                    </MudItem>\n\n                                    <MudItem xs=\"7\">\n                                        <input type=\"text\" id=\"@lab\" value=\"@LocalSerialHostSettingValue?.ToString()\" @onchange=\"@((ChangeEventArgs e) => OnChange(e, LocalSerialHostSetting, parent, false))\" class=\"form-control\" />\n                                    </MudItem>\n                                </MudGrid>\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n\n        <MudItem Class=\"mt-5\" xs=\"12\">\n            <MudButton Class=\"mb-10\" OnClick=\"Save\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Save</MudButton>\n            @if (settingsSaved)\n            {\n                <MudText Color=\"MudBlazor.Color.Success\">@message</MudText>\n            }\n        </MudItem>\n    </MudContainer>\n</MudPaper>\n\n\n\n@code {\n\n    bool settingsSaved = false;\n    string message;\n    bool isLoadingLights = false;\n    string selectedPort = \"\";\n\n    protected override async Task OnInitializedAsync()\n    {\n        if (!appState.SignedIn)\n        {\n            NavManager.NavigateTo(\"/\");\n        }\n        try\n        {\n            appState.OnChange += RaiseStateHasChanged;\n\n            await GetSerialHosts();\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred loading Local Serial Host Page\");\n            throw;\n        }\n        await Task.CompletedTask;\n    }\n\n    private void Save()\n    {\n        try\n        {\n            SettingsService.SaveSettings(appState.Config);\n            _mediator.Send(new InitializeCommand { AppState = appState });\n\n            message = \"Settings Saved\";\n            settingsSaved = true;\n            _logger.LogInformation(\"Settings Saved from Local Serial Host Page\");\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred Saving Local Serial Host Settings\");\n            throw;\n        }\n    }\n\n    private void OnChange(ChangeEventArgs e, object setting, object LocalSerialHostSettingValue, bool directChangeValue = false)\n    {\n        var newSetting = e.Value;\n\n        if (directChangeValue)\n        {\n            string settingAsString = (string)setting;\n            settingAsString = (string)newSetting;\n        }\n        else\n        {\n            PropertyInfo propInfo = (PropertyInfo)setting;\n\n            try\n            {\n                propInfo.SetValue(LocalSerialHostSettingValue, newSetting);\n            }\n            catch (TargetException TargetEx)\n            {\n                _logger.LogError(TargetEx, \"New value is wrong type\");\n            }\n        }\n    }\n\n    private void OnChange(string e)\n    {\n        var port = e;\n        appState.Config.LightSettings.LocalSerialHost.SelectedItemId = port;\n        appState.SetLocalSerialHost(port);\n        _logger.LogInformation($\"Selected Serial Host Set: {port}\");\n    }\n\n    public async Task GetSerialHosts()\n    {\n        if (appState.Config.LightSettings.LocalSerialHost.IsEnabled)\n        {\n            try\n            {\n                isLoadingLights = true;\n                _logger.LogInformation(\"Local Serial Hosts Retrieval Initialized\");\n                appState.SetLocalSerialHosts(await _mediator.Send(new Core.LocalSerialHostServices.GetPortCommand()));\n\n                if (string.IsNullOrEmpty(appState.Config.LightSettings.LocalSerialHost.SelectedItemId) && appState.LocalSerialHosts.Count() > 0)\n                {\n                    appState.Config.LightSettings.LocalSerialHost.SelectedItemId = appState.LocalSerialHosts.FirstOrDefault();\n                }\n                else\n                {\n                    selectedPort = appState.LocalSerialHosts.Where(a => a == appState.Config.LightSettings.LocalSerialHost.SelectedItemId).FirstOrDefault();\n                }\n\n                _logger.LogInformation(\"Local Serial Hosts Retrieval Successful\");\n                isLoadingLights = false;\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error occurred finding Serial Hosts\");\n                throw;\n            }\n        }\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Logs.razor",
    "content": "﻿@page \"/logs\"\n@using System.IO\n\n@inject ILogger<Logs> _logger\n@inject IJSRuntime js\n@inject Microsoft.Extensions.Configuration.IConfiguration _configuration\n\n\n\n<MudTabs Elevation=\"0\" Outlined=\"true\">\n    <MudTabPanel Text=\"Log Files\">\n         <table class=\"table\">\n                <thead>\n                    <tr>\n                        <th></th>\n                        <th>FileName</th>\n                        <th>Size</th>\n                        <th>Creation Time</th>\n                        <th>Last Access</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    @lock (logFilesLockObject)\n                    {\n                        foreach (var logFile in LogFiles.OrderByDescending(a => a.CreationTime))\n                        {\n                            <tr>\n                                <th scope=\"row\">\n                                    <a href=\"\" @onclick:preventDefault @onclick=\"@((ev) => DownloadLogs(logFile.Name))\" title=\"Click to Download @logFile.Name\">Open</a>\n                                </th>\n                                <th>@logFile.Name</th>\n                                <td>@logFile.Length</td>\n                                <td>@logFile.CreationTime</td>\n                                <td>@logFile.LastAccessTime</td>\n                            </tr>\n\n                        }\n                    }\n                </tbody>\n            </table>\n    </MudTabPanel>\n    <MudTabPanel Text=\"Live Logs\">\n         <table class=\"table\">\n                <thead>\n                    <tr>\n                        <th>Timestamp</th>\n                        <th>Level</th>\n                        <th>Message</th>\n\n                    </tr>\n                </thead>\n                <tbody>\n                    @lock (logsLockObject)\n                    {\n                        foreach (var eEvent in InformationLogs.OrderByDescending(a => a.Timestamp))\n                        {\n                            string style = null;\n                            switch (eEvent.Level)\n                            {\n                                case Serilog.Events.LogEventLevel.Warning:\n                                    style = \"background-color:yellow; color:red\";\n                                    break;\n                                case Serilog.Events.LogEventLevel.Error:\n                                    style = \"background-color:red; color:yellow\";\n                                    break;\n                                case Serilog.Events.LogEventLevel.Fatal:\n                                    style = \"background-color:red; color:white\";\n                                    break;\n                                default:\n\n                                    break;\n                            }\n                            <tr style=\"@style\">\n                                <th scope=\"row\">@eEvent.Timestamp</th>\n                                <td>@eEvent.Level</td>\n                                <td>@eEvent.RenderMessage()</td>\n                            </tr>\n\n                        }\n                    }\n                </tbody>\n            </table>\n    </MudTabPanel>\n</MudTabs>\n\n@code {\n    string selectedTab = \"logfiles\";\n    static object logsLockObject = new();\n    static object logFilesLockObject = new();\n\n    public string LogFilePath { get; set; }\n\n    private Queue<Serilog.Events.LogEvent> InformationLogs = new(25);\n    List<System.IO.FileInfo> LogFiles = new();\n\n    private System.IO.FileSystemWatcher _watcher;\n\n    protected override Task OnInitializedAsync()\n    {\n        PresenceEventsLogSink.PresenceEventsLogHandler += Handler;\n\n        InitializeFileWatcher();\n\n        return base.OnInitializedAsync();\n    }\n    private void OnSelectedTabChanged(string name)\n    {\n        selectedTab = name;\n    }\n\n\n    private void InitializeFileWatcher()\n    {\n        //TODO:  May consider making this a bit mode robust in the future.. Assumes this config\n        //       IS always the second item in the config file.\n\n        LogFilePath = _configuration[\"Serilog:WriteTo:1:Args:Path\"];\n\n        if (string.IsNullOrWhiteSpace(LogFilePath))\n            return;\n        LogFilePath = Environment.ExpandEnvironmentVariables(LogFilePath);\n        if (LogFilePath.Contains('/'))\n            LogFilePath = LogFilePath.Replace('/', '\\\\');\n\n        var fi = new FileInfo(LogFilePath);\n        if (!string.IsNullOrWhiteSpace(fi.Extension))\n        {\n            LogFilePath = fi.DirectoryName;\n        }\n\n        var di = new System.IO.DirectoryInfo(LogFilePath);\n\n        if (di.Exists)\n        {\n            di.GetFiles().ToList().ForEach(d => LogFiles.Add(d));\n        }\n        else\n        {\n            di.Create();\n        }\n\n        _watcher = new System.IO.FileSystemWatcher(LogFilePath);\n\n        _watcher.Deleted += Watcher_Changed;\n        _watcher.Created += Watcher_Changed;\n        _watcher.Changed += Watcher_Changed;\n\n        _watcher.EnableRaisingEvents = true;\n\n    }\n\n    private void Watcher_Changed(object sender, System.IO.FileSystemEventArgs e)\n    {\n        switch (e.ChangeType)\n        {\n            case System.IO.WatcherChangeTypes.Created:\n                lock (logFilesLockObject)\n                {\n                    LogFiles.Add(new System.IO.FileInfo(e.FullPath));\n                }\n                InvokeAsync(() => StateHasChanged());\n                break;\n\n            case System.IO.WatcherChangeTypes.Changed:\n                lock (logFilesLockObject)\n                {\n                    LogFiles.RemoveAll(A => A.Name.Equals(e.Name, StringComparison.CurrentCultureIgnoreCase));\n                    LogFiles.Add(new System.IO.FileInfo(e.FullPath));\n                }\n                InvokeAsync(() => StateHasChanged());\n                break;\n\n            case System.IO.WatcherChangeTypes.Deleted:\n                lock (logFilesLockObject)\n                {\n                    LogFiles.RemoveAll(A => A.Name.Equals(e.Name, StringComparison.CurrentCultureIgnoreCase));\n                }\n                InvokeAsync(() => StateHasChanged());\n                break;\n        }\n\n    }\n\n    private void Handler(object sender, Serilog.Events.LogEvent e)\n    {\n        lock (logsLockObject)\n        {\n            InformationLogs.Enqueue(e);\n        }\n        InvokeAsync(() => StateHasChanged());\n    }\n\n    async Task DownloadLogs(string filename)\n    {\n        string fileContents;\n        using (var fs = new System.IO.FileStream(\n                System.IO.Path.Combine(LogFilePath, filename),\n                System.IO.FileMode.Open,\n                System.IO.FileAccess.Read,\n                System.IO.FileShare.ReadWrite))\n        {\n            var b = new byte[fs.Length];\n\n            fs.Read(b, 0, b.Length);\n            fileContents = Convert.ToBase64String(b);\n        }\n\n        await js.InvokeAsync<object>(\n                   \"saveAsFile\",\n                   filename,\n                   fileContents);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Settings.razor",
    "content": "﻿@page \"/settings\"\n\n@inject IJSRuntime js\n@inject ILogger<Settings> _logger;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.ExtraExtraLarge\" Style=\"text-align:center\">\n        <MudText Typo=\"Typo.h3\">Settings</MudText>   <MudLink Typo=\"Typo.body1\" Href=\"\" title=\"Click to download configuration file\" @onclick=\"@((ev) => DownloadSettings(@localConfigurationPath))\" Align=\"Align.Left\">Download File</MudLink>\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n            <MudItem xs=\"12\">\n                <MudText Typo=\"Typo.h5\">Microsoft Entra / Azure AD</MudText>\n            </MudItem>\n            <br />\n            <MudItem xs=\"12\">\n                <MudGrid Spacing=\"2\">\n                    <MudItem xs=\"12\" sm=\"6\">\n                        <MudTextField Label=\"Tenant ID\"\n                                    Variant=\"Variant.Outlined\"\n                                    @bind-Value=\"@appState.Config.AADSettings.TenantId\" \n                                    Placeholder=\"common or tenant GUID\" />\n                    </MudItem>\n\n                    <MudItem xs=\"12\" sm=\"6\">\n                        <MudTextField Label=\"Client ID\"\n                                    Variant=\"Variant.Outlined\"\n                                    @bind-Value=\"@appState.Config.AADSettings.ClientId\"\n                                    Placeholder=\"Application (client) ID GUID\" />\n                    </MudItem>\n\n                    <MudItem xs=\"12\">\n                        <MudTextField Label=\"Instance\"\n                                    Variant=\"Variant.Outlined\"\n                                    @bind-Value=\"@appState.Config.AADSettings.Instance\"\n                                    Placeholder=\"https://login.microsoftonline.com/\" />\n                    </MudItem>\n\n                    <MudItem xs=\"12\">\n                        <MudTextField Label=\"Redirect URI\"\n                                    Variant=\"Variant.Outlined\"\n                                    @bind-Value=\"@appState.Config.AADSettings.RedirectUri\"\n                                    Placeholder=\"http://localhost\" />\n                    </MudItem>\n\n                </MudGrid>\n\n\n            </MudItem>\n            <MudItem xs=\"6\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.SyncLights\" Label=\"Light Sync Enabled\"></MudCheckBox>\n            </MudItem>\n            <MudItem xs=\"6\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.UseWorkingHours\" Label=\"Configure Working Hours\"></MudCheckBox>\n            </MudItem>\n            @if (appState.Config.LightSettings.UseWorkingHours)\n            {\n                <MudItem xs=\"12\">\n                    <MudText Typo=\"Typo.h5\">Working Days</MudText>\n                </MudItem>\n                <br />\n\n                <br />\n                <MudItem xs=\"12\">\n                    <MudGrid Justify=\"Justify.Center\">\n                        <MudItem xs=\"1\">\n                            <MudToggleIconButton Style=@(@Sunday ? \"background-color: #594ae2ff; color: white !important\" : \"\") @bind-Toggled=\"@Sunday\" Color=\"@MudBlazor.Color.Default\" ToggledColor=\"@MudBlazor.Color.Primary\">Sun</MudToggleIconButton>\n                        </MudItem>\n                        <MudItem xs=\"1\">\n                            <MudToggleIconButton Style=@(@Monday ? \"background-color: #594ae2ff; color: white !important\" : \"\") @bind-Toggled=\"@Monday\" Color=\"@MudBlazor.Color.Default\" ToggledColor=\"@MudBlazor.Color.Primary\">Mon</MudToggleIconButton>\n                        </MudItem>\n                        <MudItem xs=\"1\">\n                            <MudToggleIconButton Style=@(@Tuesday ? \"background-color: #594ae2ff; color: white !important\" : \"\") @bind-Toggled=\"@Tuesday\" Color=\"@MudBlazor.Color.Default\" ToggledColor=\"@MudBlazor.Color.Primary\">Tues</MudToggleIconButton>\n                        </MudItem>\n                        <MudItem xs=\"1\">\n                            <MudToggleIconButton Style=@(@Wednesday ? \"background-color: #594ae2ff; color: white !important\" : \"\") @bind-Toggled=\"@Wednesday\" Color=\"@MudBlazor.Color.Default\" ToggledColor=\"@MudBlazor.Color.Primary\">Wed</MudToggleIconButton>\n                        </MudItem>\n                        <MudItem xs=\"1\">\n                            <MudToggleIconButton Style=@(@Thursday ? \"background-color: #594ae2ff; color: white !important\" : \"\") @bind-Toggled=\"@Thursday\" Color=\"@MudBlazor.Color.Default\" ToggledColor=\"@MudBlazor.Color.Primary\">Thu</MudToggleIconButton>\n                        </MudItem>\n                        <MudItem xs=\"1\">\n                            <MudToggleIconButton Style=@(@Friday ? \"background-color: #594ae2ff; color: white !important\" : \"\") @bind-Toggled=\"@Friday\" Color=\"@MudBlazor.Color.Default\" ToggledColor=\"@MudBlazor.Color.Primary\">Fri</MudToggleIconButton>\n                        </MudItem>\n                        <MudItem xs=\"1\">\n                            <MudToggleIconButton Style=@(@Saturday ? \"background-color: #594ae2ff; color: white !important\" : \"\") @bind-Toggled=\"@Saturday\" Color=\"@MudBlazor.Color.Default\" ToggledColor=\"@MudBlazor.Color.Primary\">Sat</MudToggleIconButton>\n                        </MudItem>\n                    </MudGrid>\n                </MudItem>\n                <br />\n\n                <br />\n                <MudItem xs=\"12\">\n                    <MudGrid Justify=\"Justify.FlexStart\" Class=\"align-center\">\n                        <MudItem xs=\"4\">\n                            <MudText Typo=\"Typo.h5\">Light Syncing Schedule (Working Hours)</MudText>\n                            <br />\n                            <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.UseAmPm\" Label=\"Use AM PM Format\"></MudCheckBox>\n                        </MudItem>\n                        <MudItem xs=\"3\">\n                            <MudTimePicker Label=\"Start Time\" AmPm=\"@appState.Config.LightSettings.UseAmPm\" @bind-Time=\"startTimeSpan\" />\n                        </MudItem>\n                        <MudItem xs=\"3\">\n                            <MudTimePicker Label=\"End Time\" AmPm=\"@appState.Config.LightSettings.UseAmPm\" @bind-Time=\"endTimeSpan\" />\n                        </MudItem>\n                    </MudGrid>\n                </MudItem>\n                <br />\n\n                <br />\n                <MudItem xs=\"12\">\n                    <MudGrid Justify=\"Justify.FlexStart\" Class=\"align-center\">\n                        <MudItem xs=\"4\">\n                            <MudText Typo=\"Typo.h5\">Light Status When After Hours Are Reached</MudText>\n                        </MudItem>\n                        <MudItem xs=\"4\">\n                            <MudRadioGroup @bind-Value=\"@appState.Config.LightSettings.HoursPassedStatus\">\n                                <MudRadio Value=\"@(\"Off\")\" Color=\"MudBlazor.Color.Default\" Dense=\"true\">Off</MudRadio>\n                                <MudRadio Value=\"@(\"White\")\" Color=\"MudBlazor.Color.Default\" Dense=\"true\">White</MudRadio>\n                                <MudRadio Value=\"@(\"Keep\")\" Color=\"MudBlazor.Color.Default\" Dense=\"true\">Keep</MudRadio>\n                            </MudRadioGroup>\n                        </MudItem>\n                    </MudGrid>\n                </MudItem>\n            }\n            <MudItem xs=\"12\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.UseDefaultBrightness\" Label=\"Lights Use Same Brightness\"></MudCheckBox>\n            </MudItem>\n            @if (appState.Config.LightSettings.UseDefaultBrightness)\n            {\n                <MudItem xs=\"12\">\n                    <MudGrid Justify=\"Justify.FlexStart\" Class=\"align-center\">\n                        <MudItem xs=\"3\">\n                            <MudText Typo=\"Typo.h5\">Brightness</MudText>\n                        </MudItem>\n                        <MudItem xs=\"6\">\n                            <MudSlider @bind-Value=\"appState.Config.LightSettings.DefaultBrightness\" Min=\"0\" Max=\"100\"></MudSlider>\n                        </MudItem>\n                        <MudItem xs=\"2\">\n                            <MudNumericField @bind-Value=\"appState.Config.LightSettings.DefaultBrightness\" Variant=\"Variant.Outlined\" Min=\"0\" Max=\"100\" Step=\"1\" />\n                        </MudItem>\n                    </MudGrid>\n                </MudItem>\n            }\n            <MudItem xs=\"12\">\n                <MudGrid Justify=\"Justify.FlexStart\" Class=\"align-center\">\n                    <MudItem xs=\"3\">\n                        <MudText Typo=\"Typo.h5\">Polling Interval</MudText>\n                    </MudItem>\n                    <MudItem xs=\"6\">\n                        <MudSlider @bind-Value=\"appState.Config.LightSettings.PollingInterval\" Step=\".1\" Min=\"1\" Max=\"5\"></MudSlider>\n                    </MudItem>\n                    <MudItem xs=\"2\">\n                        <MudNumericField @bind-Value=\"appState.Config.LightSettings.PollingInterval\" Variant=\"Variant.Outlined\" Format=\"F1\" Min=\"1.0\" Max=\"5.0\" Step=\".1\" />\n                    </MudItem>\n                </MudGrid>\n            </MudItem>\n            @if (appState.Config.AppType == \"Desktop\")\n            {\n                <MudItem xs=\"12\">\n                    <MudGrid Justify=\"Justify.Center\" Class=\"align-center\">\n                        <MudItem xs=\"3\">\n                            <MudText Typo=\"Typo.h5\">Icon Type</MudText>\n                        </MudItem>\n                        <MudItem xs=\"4\">\n                            <MudRadioGroup @bind-Value=\"@appState.Config.IconType\">\n                                <MudRadio Value=\"@(\"Transparent\")\" Color=\"MudBlazor.Color.Default\" Dense=\"true\">Transparent</MudRadio>\n                                <MudRadio Value=\"@(\"White\")\" Color=\"MudBlazor.Color.Default\" Dense=\"true\">White</MudRadio>\n                            </MudRadioGroup>\n                        </MudItem>\n                        <MudItem xs=\"12\">\n                            <MudCheckBox @bind-Value=\"@appState.Config.StartMinimized\" Label=\"Start Minimized\"></MudCheckBox>\n                        </MudItem>\n                    </MudGrid>\n                </MudItem>\n            }\n            <MudItem xs=\"12\">\n                <MudButton Class=\"mb-10\" OnClick=\"SaveSettings\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Save Settings</MudButton>\n                @if (settingsSaved)\n                {\n                    <MudText Color=\"MudBlazor.Color.Success\">@message</MudText>\n                }\n            </MudItem>\n        </MudGrid>\n    </MudContainer>\n</MudPaper>\n\n@code {\n    bool settingsSaved = false;\n    string message;\n    string localConfigurationPath;\n\n    bool Monday;\n    bool Tuesday;\n    bool Wednesday;\n    bool Thursday;\n    bool Friday;\n    bool Saturday;\n    bool Sunday;\n\n\n    TimeSpan? startTimeSpan;\n    TimeSpan? endTimeSpan;\n\n    protected override async Task OnInitializedAsync()\n    {\n        appState.Config.LightSettings.WorkingHoursStartTimeAsDate = string.IsNullOrEmpty(appState.Config.LightSettings.WorkingHoursStartTime) ? null : DateTime.Parse(appState.Config.LightSettings.WorkingHoursStartTime, null);\n        appState.Config.LightSettings.WorkingHoursEndTimeAsDate = string.IsNullOrEmpty(appState.Config.LightSettings.WorkingHoursEndTime) ? null : DateTime.Parse(appState.Config.LightSettings.WorkingHoursEndTime, null);\n\n        PopulateWorkingDays();\n\n        startTimeSpan = appState.Config.LightSettings.WorkingHoursStartTimeAsDate == null ? null : appState.Config.LightSettings.WorkingHoursStartTimeAsDate.Value.TimeOfDay;\n        endTimeSpan = appState.Config.LightSettings.WorkingHoursEndTimeAsDate == null ? null : appState.Config.LightSettings.WorkingHoursEndTimeAsDate.Value.TimeOfDay;\n\n        localConfigurationPath = new System.IO.FileInfo(SettingsService.GetSettingsFileLocation()).FullName;\n\n        await Task.CompletedTask;\n    }\n\n    void SaveSettings()\n    {\n        try\n        {\n            SetWorkingDays();\n\n\n            appState.Config.LightSettings.WorkingHoursStartTime = startTimeSpan.ToString();\n            appState.Config.LightSettings.WorkingHoursEndTime = endTimeSpan.ToString();\n\n            SettingsService.SaveSettings(appState.Config);\n            message = \"Settings Saved\";\n            settingsSaved = true;\n            _logger.LogInformation(\"Settings Saved from Settings Page\");\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred Saving Settings from Settings Page\");\n            throw;\n        }\n    }\n\n\n    private void PopulateWorkingDays()\n    {\n        if (!string.IsNullOrEmpty(appState.Config.LightSettings.WorkingDays))\n        {\n            if (appState.Config.LightSettings.WorkingDays.Contains(\"Monday\", StringComparison.OrdinalIgnoreCase))\n            {\n                Monday = true;\n            }\n\n            if (appState.Config.LightSettings.WorkingDays.Contains(\"Tuesday\", StringComparison.OrdinalIgnoreCase))\n            {\n                Tuesday = true;\n            }\n\n            if (appState.Config.LightSettings.WorkingDays.Contains(\"Wednesday\", StringComparison.OrdinalIgnoreCase))\n            {\n                Wednesday = true;\n            }\n\n            if (appState.Config.LightSettings.WorkingDays.Contains(\"Thursday\", StringComparison.OrdinalIgnoreCase))\n            {\n                Thursday = true;\n            }\n\n            if (appState.Config.LightSettings.WorkingDays.Contains(\"Friday\", StringComparison.OrdinalIgnoreCase))\n            {\n                Friday = true;\n            }\n\n            if (appState.Config.LightSettings.WorkingDays.Contains(\"Saturday\", StringComparison.OrdinalIgnoreCase))\n            {\n                Saturday = true;\n            }\n\n            if (appState.Config.LightSettings.WorkingDays.Contains(\"Sunday\", StringComparison.OrdinalIgnoreCase))\n            {\n                Sunday = true;\n            }\n        }\n    }\n\n    private void SetWorkingDays()\n    {\n        List<string> days = new List<string>();\n\n        if (Monday)\n        {\n            days.Add(\"Monday\");\n        }\n\n        if (Tuesday)\n        {\n            days.Add(\"Tuesday\");\n        }\n\n        if (Wednesday)\n        {\n            days.Add(\"Wednesday\");\n        }\n\n        if (Thursday)\n        {\n            days.Add(\"Thursday\");\n        }\n\n        if (Friday)\n        {\n            days.Add(\"Friday\");\n        }\n\n        if (Saturday)\n        {\n            days.Add(\"Saturday\");\n        }\n\n        if (Sunday)\n        {\n            days.Add(\"Sunday\");\n        }\n\n        appState.Config.LightSettings.WorkingDays = string.Join(\"|\", days);\n    }\n\n    async Task DownloadSettings(string filename)\n    {\n        string fileContents;\n        using (var fs = new System.IO.FileStream(\n                filename,\n                System.IO.FileMode.Open,\n                System.IO.FileAccess.Read,\n                System.IO.FileShare.ReadWrite))\n        {\n            var b = new byte[fs.Length];\n\n            fs.Read(b, 0, b.Length);\n            fileContents = Convert.ToBase64String(b);\n        }\n\n        await js.InvokeAsync<object>(\n                   \"saveAsFile\",\n                   filename,\n                   fileContents);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Wiz.razor",
    "content": "﻿@page \"/wiz\"\n\n\n@inject ILogger<Wiz> _logger;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.ExtraExtraLarge\" Style=\"text-align:center\">\n        <MudText Typo=\"Typo.h3\">Configure Wiz</MudText>\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n            <MudItem xs=\"12\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.Wiz.IsEnabled\" Label=\"Connect to Wiz\"></MudCheckBox>\n            </MudItem>\n        </MudGrid>\n        @if (appState.Config.LightSettings.Wiz.IsEnabled)\n        {\n            <MudGrid Justify=\"Justify.Center\">\n                <MudItem xs=\"4\">\n                    <EditForm Model=\"@appState.Config\" OnValidSubmit=\"GetLight\">\n                        <DataAnnotationsValidator />\n                        <MudTextField Label=\"IP Address\"\n                                      Variant=\"Variant.Outlined\"\n                                      @bind-Value=\"@appState.Config.LightSettings.Wiz.IPAddress\"\n                                      For=\"@(() => appState.Config.LightSettings.Wiz.IPAddress)\"\n                                      Validation=\"true\" />\n                        <br />\n\n                        <MudButton ButtonType=\"MudBlazor.ButtonType.Submit\"\n                                   Variant=\"Variant.Filled\"\n                                   Color=\"MudBlazor.Color.Primary\">Get Light</MudButton>\n                    </EditForm>\n                </MudItem>\n            </MudGrid>\n\n            @if (showWizMessage)\n            {\n                <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                    <MudItem xs=\"12\">\n                        <MudText Class=@wizMessageClass Typo=\"Typo.body1\">@wizMessage</MudText>\n                    </MudItem>\n                </MudGrid>\n            }\n            if (isLoadingLights)\n            {\n                <br />\n\n                <br />\n                <Circle Center=\"true\" />\n            }\n            else\n            {\n                @if (appState.WizLight != null)\n                {\n                    <MudGrid Class=\"mt-5 align-center\" Justify=\"Justify.FlexStart\">\n                        <MudItem xs=\"3\">\n                            <MudText Typo=\"Typo.h5\">Brightness</MudText>\n                        </MudItem>\n                        <MudItem xs=\"6\">\n                            <MudSlider @bind-Value=\"appState.Config.LightSettings.Wiz.Brightness\" Min=\"0\" Max=\"100\"></MudSlider>\n                        </MudItem>\n                        <MudItem xs=\"2\">\n                            <MudNumericField @bind-Value=\"appState.Config.LightSettings.Wiz.Brightness\" Variant=\"Variant.Outlined\" Min=\"0\" Max=\"100\" Step=\"1\" />\n                        </MudItem>\n                    </MudGrid>\n\n                    <Statuses Light=\"@appState.Config.LightSettings.Wiz\"></Statuses>\n                }\n            }\n        }\n        <MudItem Class=\"mt-5\" xs=\"12\">\n            <MudButton Class=\"mb-10\" OnClick=\"Save\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Save</MudButton>\n            @if (settingsSaved)\n            {\n                <MudText Color=\"MudBlazor.Color.Success\">@message</MudText>\n            }\n        </MudItem>\n    </MudContainer>\n</MudPaper>\n\n@code {\n\n    bool settingsSaved = false;\n    bool isLoadingLights = false;\n    string message;\n    string wizMessageClass;\n\n    bool showWizMessage = false;\n    string wizMessage;\n    string selectedLightLabel = \"\";\n    string ipAddress = \"\";\n    protected override async Task OnInitializedAsync()\n    {\n        try\n        {\n            appState.OnChange += RaiseStateHasChanged;\n\n            if (!appState.SignedIn)\n            {\n                NavManager.NavigateTo(\"/\");\n            }\n\n            await GetLight();\n        }\n\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred loading Wiz\");\n            throw;\n        }\n        await Task.CompletedTask;\n    }\n\n    private async Task Save()\n    {\n        try\n        {\n            await SettingsService.SaveSettings(appState.Config);\n            message = \"Settings Saved\";\n            settingsSaved = true;\n            _logger.LogInformation(\"Settings Saved from Wiz Page\");\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred Saving Wiz Settings\");\n            throw;\n        }\n    }\n\n    public async Task GetLight()\n    {\n        if (appState.Config.LightSettings.Wiz.IsEnabled && !string.IsNullOrEmpty(appState.Config.LightSettings.Wiz.IPAddress))\n        {\n            if (System.Text.RegularExpressions.Regex.IsMatch(\n                 appState.Config.LightSettings.Wiz.IPAddress,\n                 @\"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b\"))\n            {\n\n                try\n                {\n                    isLoadingLights = true;\n                    _logger.LogInformation(\"Wiz Light Retrieval Initialized\");\n                    appState.SetWizLight(await _mediator.Send(new Core.WizServices.GetLightCommand()));\n\n                    if (string.IsNullOrEmpty(appState.Config.LightSettings.Wiz.SelectedItemId) && appState.WizLight != null)\n                    {\n                        appState.Config.LightSettings.Wiz.SelectedItemId = appState.WizLight.MacAddress;\n                    }\n                    else\n                    {\n                        selectedLightLabel = appState.WizLight.LightName;\n                    }\n                    _logger.LogInformation(\"Wiz Light Retrieval Successful\");\n                    showWizMessage = true;\n                    wizMessage = $\"Connected to {appState.WizLight.LightName}\";\n                    wizMessageClass = \"text-success\";\n                    isLoadingLights = false;\n                }\n                catch (Exception ex)\n                {\n                    _logger.LogError(ex, \"Error occurred Finding Wiz Lights\");\n                    wizMessage = \"Error Occurred finding light, please try another IP Address\";\n                    showWizMessage = true;\n                    wizMessageClass = \"text-danger\";\n                    throw;\n                }\n            }\n            else\n            {\n                wizMessage = \"Not a valid IP Address\";\n                showWizMessage = true;\n                wizMessageClass = \"text-danger\";\n            }\n        }\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Yeelight.razor",
    "content": "﻿@page \"/yeelight\"\n\n@inject ILogger<CustomApiSetup> _logger;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidth.ExtraExtraLarge\" Style=\"text-align:center\">\n        <MudText Typo=\"Typo.h3\">Configure Yeelight</MudText>\n        <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n            <MudItem xs=\"12\">\n                <MudCheckBox @bind-Value=\"@appState.Config.LightSettings.Yeelight.IsEnabled\" Label=\"Connect to Yeelight\"></MudCheckBox>\n            </MudItem>\n        </MudGrid>\n        @if (appState.Config.LightSettings.Yeelight.IsEnabled)\n        {\n            <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                <MudItem xs=\"12\">\n                    <MudButton OnClick=\"GetYeeLights\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Find Yeelights</MudButton>\n                </MudItem>\n            </MudGrid>\n            if (isLoadingLights)\n            {\n                <br />\n\n                <br />\n                <Circle Center=\"true\" />\n            }\n            else\n            {\n                @if (appState.YeelightLights != null)\n                {\n                    <MudGrid Class=\"mt-5\" Justify=\"Justify.Center\">\n                        <MudItem xs=\"4\">\n                            <MudSelect Value=@($\"{selectedLightLabel}\") Dense=\"true\" T=\"string\" Variant=\"Variant.Outlined\" ValueChanged=\"OnChange\">\n                                <MudSelectItem Disabled=\"true\" Value=\"@(\"Select\")\" />\n                                @foreach (var light in appState.YeelightLights)\n                                {\n                                    <MudSelectItem Value=\"@light.Id\">@light.Hostname</MudSelectItem>\n                                }\n                            </MudSelect>\n                        </MudItem>\n                    </MudGrid>\n\n                    <MudGrid Class=\"mt-5 align-center\" Justify=\"Justify.FlexStart\">\n                        <MudItem xs=\"3\">\n                            <MudText Typo=\"Typo.h5\">Brightness</MudText>\n                        </MudItem>\n                        <MudItem xs=\"6\">\n                            <MudSlider @bind-Value=\"appState.Config.LightSettings.Yeelight.Brightness\" Min=\"0\" Max=\"100\"></MudSlider>\n                        </MudItem>\n                        <MudItem xs=\"2\">\n                            <MudNumericField @bind-Value=\"appState.Config.LightSettings.Yeelight.Brightness\" Variant=\"Variant.Outlined\" Min=\"0\" Max=\"100\" Step=\"1\" />\n                        </MudItem>\n                    </MudGrid>\n\n                    <Statuses Light=\"@appState.Config.LightSettings.Yeelight\"></Statuses>\n                }\n            }\n        }\n        <MudItem Class=\"mt-5\" xs=\"12\">\n            <MudButton Class=\"mb-10\" OnClick=\"Save\" Variant=\"Variant.Filled\" Color=\"MudBlazor.Color.Primary\">Save</MudButton>\n            @if (settingsSaved)\n            {\n                <MudText Color=\"MudBlazor.Color.Success\">@message</MudText>\n            }\n        </MudItem>\n    </MudContainer>\n</MudPaper>\n\n@code {\n    bool settingsSaved = false;\n    string message;\n    bool isLoadingLights = false;\n    string selectedLightLabel = \"\";\n\n    protected override async Task OnInitializedAsync()\n    {\n        try\n        {\n            appState.OnChange += RaiseStateHasChanged;\n\n            if (!appState.SignedIn)\n            {\n                NavManager.NavigateTo(\"/\");\n            }\n\n            await GetYeeLights();\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred loading Yeelight\");\n            throw;\n        }\n        await Task.CompletedTask;\n    }\n\n    private async Task Save()\n    {\n        try\n        {\n            await SettingsService.SaveSettings(appState.Config);\n            message = \"Settings Saved\";\n            settingsSaved = true;\n            _logger.LogInformation(\"Settings Saved from Yeelight Page\");\n        }\n        catch (Exception e)\n        {\n            _logger.LogError(e, \"Error Occurred Saving Yeelight Settings\");\n            throw;\n        }\n    }\n\n    private void OnChange(string e)\n    {\n        var light = e;\n        appState.Config.LightSettings.Yeelight.SelectedItemId = light;\n        appState.SetYeelightLight(light);\n        _logger.LogInformation($\"Selected Yeelight Light Set: {light}\");\n    }\n\n    public async Task GetYeeLights()\n    {\n        if (@appState.Config.LightSettings.Yeelight.IsEnabled)\n        {\n            try\n            {\n                isLoadingLights = true;\n                _logger.LogInformation(\"Yeelight Light Retrieval Initialized\");\n                appState.SetYeelightLights(await _mediator.Send(new Core.YeelightServices.GetLightCommand()));\n\n                if (string.IsNullOrEmpty(appState.Config.LightSettings.Yeelight.SelectedItemId) && appState.YeelightLights.Count() > 0)\n                {\n                    appState.Config.LightSettings.Yeelight.SelectedItemId = appState.YeelightLights.FirstOrDefault().Id;\n                }\n                else\n                {\n                    selectedLightLabel = appState.YeelightLights.Where(a => a.Id == appState.Config.LightSettings.Yeelight.SelectedItemId).FirstOrDefault()?.Hostname;\n                }\n                _logger.LogInformation(\"Yeelight Light Retrieval Successful\");\n                isLoadingLights = false;\n\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error occurred Finding YeeLights\");\n                throw;\n            }\n        }\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n        }\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/PresenceLightClientApp.razor",
    "content": "﻿<Router AppAssembly=\"@typeof(AppInfo).Assembly\">\n    <Found Context=\"routeData\">\n        <RouteView RouteData=\"@routeData\" DefaultLayout=\"@typeof(MainLayout)\" />\n    </Found>\n</Router>"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Shared/Confirm.razor",
    "content": "﻿<MudDialog>\n    <DialogActions>\n        <MudButton Color=\"MudBlazor.Color.Primary\" OnClick=\"Submit\">Ok</MudButton>\n    </DialogActions>\n</MudDialog>\n@code {\n    [CascadingParameter]\n    private IMudDialogInstance MudDialog { get; set; }\n\n    void Submit() => MudDialog.Close(DialogResult.Ok(true));\n}"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Shared/LoginDisplay.razor",
    "content": "﻿@inject AppState appState\n@inject NavigationManager NavManager\n\n@if (appState.Config.AppType == \"Desktop\")\n{\n    @if (appState.SignedIn && appState.User != null)\n    {\n        <MudMenu AnchorOrigin=\"Origin.CenterCenter\" Dense=\"true\" Class=\"mt-5 ml-4\">\n            <ActivatorContent>\n                <MudText Typo=\"Typo.body2\" Class=\"px-4 py-2\"> Hello, @appState.User.DisplayName!</MudText>\n            </ActivatorContent>\n            <ChildContent>\n                <MudListItem T=\"string\" Text=\"Logout\" Icon=\"@Icons.Material.Filled.Logout\" OnClick=\"SignOut\" />\n            </ChildContent>\n        </MudMenu>\n    }\n    else\n    { }\n}\nelse\n{\n    <AuthorizeView>\n        <Authorized>\n            <MudMenu AnchorOrigin=\"Origin.CenterCenter\" Dense=\"true\" Class=\"mt-5 ml-4\">\n                <ActivatorContent>\n                    <MudText Typo=\"Typo.body2\" Class=\"px-4 py-2\"> Hello, @context.User.Identity.Name!</MudText>\n                </ActivatorContent>\n                <ChildContent>\n                    <div tabindex=\"0\" class=\"mud-list-item mud-list-item-dense mud-list-item-gutters mud-list-item-clickable mud-ripple\">\n                        <div class=\"mud-list-item-icon\">\n                            <svg class=\"mud-icon-root mud-svg-icon mud-inherit-text mud-icon-size-medium\" focusable=\"false\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n                                <path d=\"M0 0h24v24H0z\" fill=\"none\"></path>\n                                <path d=\"M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z\"></path>\n                            </svg>\n                        </div>\n                        <div class=\"mud-list-item-text \">\n                            <MudLink Class=\"mud-typography mud-typography-body2 mud-inherit-text\" Href=\"MicrosoftIdentity/Account/SignOut\">Log Out</MudLink>\n                        </div>\n                    </div>\n                </ChildContent>\n            </MudMenu>\n        </Authorized>\n        <NotAuthorized>\n            <MudLink Href=\"MicrosoftIdentity/Account/SignIn\">Log in</MudLink>\n        </NotAuthorized>\n    </AuthorizeView>\n}\n\n@code\n{\n    protected override void OnInitialized()\n    {\n        appState.OnChange += RaiseStateHasChanged;\n    }\n\n    private void SignIn()\n    {\n        appState.SignInRequested = true;\n    }\n\n    private void SignOut()\n    {\n        if (appState.Config.AppType == \"Desktop\")\n        {\n            appState.SignOutRequested = true;\n            NavManager.NavigateTo(\"/\");\n        }\n        else\n        {\n            NavManager.NavigateTo(\"MicrosoftIdentity/Account/SignOut\");\n        }\n    }\n\n    public void Dispose()\n    {\n        appState.OnChange -= RaiseStateHasChanged;\n    }\n\n    private void RaiseStateHasChanged()\n    {\n        InvokeAsync(StateHasChanged);\n    }\n}"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Shared/Statuses.razor",
    "content": "﻿<br />\n<br />\n<MudExpansionPanels>\n    <MudExpansionPanel>\n        <TitleContent>\n            <MudText Typo=\"Typo.h6\" Color=\"MudBlazor.Color.Primary\">Custom Colors</MudText>\n        </TitleContent>\n        <ChildContent>\n            <MudGrid Justify=\"Justify.Center\">\n                <MudItem xs=\"12\">                   \n\n                    <MudCheckBox @bind-Value=\"@Light.UseActivityStatus\" Label=\"Use Activity Statuses\"></MudCheckBox>\n                </MudItem>\n            </MudGrid>\n            <br />\n            <br />\n\n            @foreach (var lightStatus in Light.Statuses.GetType().GetProperties().OrderBy(a => a.Name))\n            {\n                @if ((!Light.UseActivityStatus && lightStatus.Name.Contains(\"Availability\")) || (Light.UseActivityStatus && lightStatus.Name.Contains(\"Activity\")))\n                {\n                    var status = (AvailabilityStatus)lightStatus.GetValue(Light.Statuses);\n\n                    <MudGrid Justify=\"Justify.Center\">\n                        <MudItem xs=\"4\">\n                            <MudText Typo=Typo.h6>@Helpers.HumanifyText(lightStatus.Name.Replace(\"Status\", \"\").Replace(\"Availability\", \"\").Replace(\"Activity\", \"\")) Color</MudText>\n                        </MudItem>\n                        <MudItem xs=\"4\">\n                            <ColorEdit Color=\"@status.Color\" @onchange=\"@((ChangeEventArgs e) => ChangeStatusColor(e, status))\" />\n                        </MudItem>\n                        <MudItem xs=\"4\">\n                            <MudCheckBox T=\"bool\" Checked=\"@status.Disabled\" ValueChanged=\"@(x => StatusDisabledOnCheck(x, status))\" Label=\"Off?\"></MudCheckBox>\n                        </MudItem>\n                    </MudGrid>\n                }\n            }\n        </ChildContent>\n    </MudExpansionPanel>\n</MudExpansionPanels>\n@code {\n    [Parameter] public BaseLight Light { get; set; }\n\n    void StatusDisabledOnCheck(bool e, object settingValue)\n    {\n        var newSetting = e;\n        AvailabilityStatus status = (AvailabilityStatus)settingValue;\n        status.Disabled = newSetting;\n    }\n\n    void ChangeStatusColor(ChangeEventArgs e, object settingValue)\n    {\n        var newSetting = e.Value;\n        AvailabilityStatus status = (AvailabilityStatus)settingValue;\n        status.Color = (string)newSetting;\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Components/_Imports.razor",
    "content": "﻿@using System.Net.Http\n@using Microsoft.AspNetCore.Authorization\n@using Microsoft.AspNetCore.Components.Authorization\n@using Microsoft.AspNetCore.Components.Forms\n@using Microsoft.AspNetCore.Components.Routing\n@using Microsoft.AspNetCore.Components.Web\n@using Microsoft.JSInterop\n@using PresenceLight.Razor\n@using PresenceLight.Razor.Components\n@using PresenceLight.Razor.Components.Shared\n@using PresenceLight.Razor.Components.Layout\n@using Microsoft.AspNetCore.Authentication\n@using PresenceLight.Core\n@using Microsoft.Extensions.Options\n@using Newtonsoft.Json\n@using Microsoft.Graph\n@using System\n@using System.Net\n@using System.Net.Http.Headers\n@using System.Threading.Tasks\n@using System.Threading\n@using BlazorPro.Spinkit\n@using Microsoft.Extensions.Logging\n@using PresenceLight.Razor.Services\n@using PresenceLight\n@using System.Reflection\n@using Blazorise\n@using MudBlazor\n@inject MediatR.IMediator _mediator\n@inject ISettingsService SettingsService\n@inject AppState appState\n@inject NavigationManager NavManager\n"
  },
  {
    "path": "src/PresenceLight.Razor/PresenceLight.Razor.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Razor\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n\n\n  <ItemGroup>\n    <SupportedPlatform Include=\"browser\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Alexa.NET\" Version=\"1.22.0\" />\n    <PackageReference Include=\"Blazorise.Bootstrap\" Version=\"1.8.7\" />\n    <PackageReference Include=\"Blazorise.Icons.FontAwesome\" Version=\"1.8.7\" />\n    <PackageReference Include=\"BlazorPro.Spinkit\" Version=\"1.2.0\" />\n    <PackageReference Include=\"DesktopBridge.Helpers\" Version=\"1.2.2\" />\n    <PackageReference Include=\"Duende.IdentityModel\" Version=\"7.1.0\" />\n    <PackageReference Include=\"Microsoft.ApplicationInsights.AspNetCore\" Version=\"2.23.0\" />\n    <PackageReference Include=\"Microsoft.ApplicationInsights.SnapshotCollector\" Version=\"1.4.6\" />\n    <PackageReference Include=\"Microsoft.AspNet.WebApi.Client\" Version=\"6.0.0\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Authentication.OpenIdConnect\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Components.Web\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.Abstractions\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Configuration.UserSecrets\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Hosting.Systemd\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Http\" Version=\"10.0.0\" />\n    <PackageReference Include=\"Microsoft.Identity.Client\" Version=\"4.79.2\" />\n    <PackageReference Include=\"Microsoft.Identity.Web\" Version=\"4.1.0\" />\n    <PackageReference Include=\"Microsoft.Identity.Web.MicrosoftGraph\" Version=\"4.1.0\" />\n    <PackageReference Include=\"Microsoft.Identity.Web.UI\" Version=\"4.1.0\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Azure.Containers.Tools.Targets\" Version=\"1.22.1\" />\n    <PackageReference Include=\"MudBlazor\" Version=\"8.14.0\" />\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.4\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\PresenceLight.Core\\PresenceLight.Core.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "src/PresenceLight.Razor/Services/AppInfo.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.Reflection;\n\nusing Microsoft.Extensions.Configuration;\n\nnamespace PresenceLight.Razor\n{\n    public class AppInfo\n    {\n        private readonly IConfiguration _config;\n        public AppInfo(IConfiguration Configuration)\n        {\n            _config = Configuration;\n        }\n\n        public static string GetInstallLocation()\n        {\n            return System.AppContext.BaseDirectory;\n        }\n\n        public static string GetInstallationDate()\n        {\n            var date = System.IO.File.GetLastWriteTime(System.AppContext.BaseDirectory);\n            return $\"{date.ToShortDateString()} {date.ToShortTimeString()}\";\n        }\n\n        public string GetApplicationVersion()\n        {\n            return _config[\"AppVersion\"];\n        }\n\n        public static string GetDotNetRuntimeInfo()\n        {\n            return typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;\n        }\n\n        public string GetAppInstallType()\n        {\n            if (Environment.GetEnvironmentVariable(\"DOTNET_RUNNING_IN_CONTAINER\") == \"true\")\n            {\n                return \"Container\";\n            }\n\n            if (_config[\"AppType\"] == \"Web\")\n            {\n                return \"Web\";\n            }\n\n            if (new DesktopBridge.Helpers().IsRunningAsUwp())\n            {\n                return \"AppPackage\";\n            }\n            else\n            {\n                return \"Standalone\";\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Services/AppVersionTelemetryInitializer.cs",
    "content": "﻿using System.Diagnostics;\n\nusing Microsoft.ApplicationInsights.Channel;\nusing Microsoft.ApplicationInsights.Extensibility;\n\nnamespace PresenceLight.Razor.Services\n{\n    public class AppVersionTelemetryInitializer : ITelemetryInitializer\n    {   \n        private readonly AppInfo _appInfo;\n\n        public AppVersionTelemetryInitializer(AppInfo appInfo)\n        {\n            _appInfo = appInfo;\n        }\n\n        public void Initialize(ITelemetry telemetry)\n        {\n            telemetry.Context.Component.Version = _appInfo.GetApplicationVersion();\n            telemetry.Context.GlobalProperties[\"App Version\"] = _appInfo.GetApplicationVersion();\n            telemetry.Context.GlobalProperties[\"App Install Type\"] = _appInfo.GetAppInstallType();\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/Services/WebAppSettingsService.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Configuration;\n\nusing Newtonsoft.Json;\n\nusing PresenceLight.Core;\n\nnamespace PresenceLight.Razor.Services\n{\n    public class WebAppSettingsService : ISettingsService\n    {\n        readonly IConfiguration _configuration;\n        private readonly AppState _appState;\n        public WebAppSettingsService(IConfiguration configuration, AppState appState)\n        {\n            _appState = appState;\n            _configuration = configuration;\n        }\n\n        public Task<bool> DeleteSettings()\n        {\n            if (File.Exists(GetSettingsFileLocation()))\n            {\n                File.Delete(GetSettingsFileLocation());\n            }\n            return Task.Run(() => true);\n        }\n\n        public async Task<bool> IsFilePresent()\n        {\n            if (!File.Exists(GetSettingsFileLocation()))\n            {\n                return false;\n            }\n            else\n            {\n                var config = await LoadSettings();\n                if (config == null)\n                {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        public async Task<BaseConfig?> LoadSettings()\n        {\n            string fileJSON = await File.ReadAllTextAsync(GetSettingsFileLocation(), Encoding.UTF8);\n            var config = JsonConvert.DeserializeObject<BaseConfig>(fileJSON);\n            _appState.SetConfig(config);\n            return config;\n        }\n\n        public async Task<bool> SaveSettings(BaseConfig data)\n        {\n            string content = JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { });\n            await File.WriteAllTextAsync(GetSettingsFileLocation(), content, Encoding.UTF8);\n            _appState.SetConfig(data);\n            return true;\n        }\n\n        public string GetSettingsFileLocation()\n        {\n\n            if (_configuration?[\"DOTNET_RUNNING_IN_CONTAINER\"] == \"true\")\n            {\n                return System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), \"config\", \"PresenceLightSettings.json\");\n            }\n            else if (Debugger.IsAttached)\n            {\n                return System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), \"PresenceLightSettings.Development.json\");\n            }\n            else\n            {\n                return System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), \"PresenceLightSettings.json\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/wwwroot/css/open-iconic/FONT-LICENSE",
    "content": "SIL OPEN FONT LICENSE Version 1.1\n\nCopyright (c) 2014 Waybury\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded,\nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "src/PresenceLight.Razor/wwwroot/css/open-iconic/ICON-LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Waybury\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "src/PresenceLight.Razor/wwwroot/css/open-iconic/README.md",
    "content": "[Open Iconic v1.1.1](http://useiconic.com/open)\n===========\n\n### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint&mdash;ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons)\n\n\n\n## What's in Open Iconic?\n\n* 223 icons designed to be legible down to 8 pixels\n* Super-light SVG files - 61.8 for the entire set \n* SVG sprite&mdash;the modern replacement for icon fonts\n* Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats\n* Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats\n* PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px.\n\n\n## Getting Started\n\n#### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections.\n\n### General Usage\n\n#### Using Open Iconic's SVGs\n\nWe like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute).\n\n```\n<img src=\"/open-iconic/svg/icon-name.svg\" alt=\"icon name\">\n```\n\n#### Using Open Iconic's SVG Sprite\n\nOpen Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack.\n\nAdding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `<svg>` *tag and a unique class name for each different icon in the* `<use>` *tag.*  \n\n```\n<svg class=\"icon\">\n  <use xlink:href=\"open-iconic.svg#account-login\" class=\"icon-account-login\"></use>\n</svg>\n```\n\nSizing icons only needs basic CSS. All the icons are in a square format, so just set the `<svg>` tag with equal width and height dimensions.\n\n```\n.icon {\n  width: 16px;\n  height: 16px;\n}\n```\n\nColoring icons is even easier. All you need to do is set the `fill` rule on the `<use>` tag.\n\n```\n.icon-account-login {\n  fill: #f00;\n}\n```\n\nTo learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/).\n\n#### Using Open Iconic's Icon Font...\n\n\n##### …with Bootstrap\n\nYou can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}`\n\n\n```\n<link href=\"/open-iconic/font/css/open-iconic-bootstrap.css\" rel=\"stylesheet\">\n```\n\n\n```\n<span class=\"oi oi-icon-name\" title=\"icon name\" aria-hidden=\"true\"></span>\n```\n\n##### …with Foundation\n\nYou can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}`\n\n```\n<link href=\"/open-iconic/font/css/open-iconic-foundation.css\" rel=\"stylesheet\">\n```\n\n\n```\n<span class=\"fi-icon-name\" title=\"icon name\" aria-hidden=\"true\"></span>\n```\n\n##### …on its own\n\nYou can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}`\n\n```\n<link href=\"/open-iconic/font/css/open-iconic.css\" rel=\"stylesheet\">\n```\n\n```\n<span class=\"oi\" data-glyph=\"icon-name\" title=\"icon name\" aria-hidden=\"true\"></span>\n```\n\n\n## License\n\n### Icons\n\nAll code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT).\n\n### Fonts\n\nAll fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web).\n"
  },
  {
    "path": "src/PresenceLight.Razor/wwwroot/css/site.css",
    "content": "@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');\n\nhtml, body {\n    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n}\n\na, .btn-link {\n    color: #0366d6;\n}\n\n.btn-primary {\n    color: #fff;\n    background-color: #1b6ec2;\n    border-color: #1861ac;\n}\n\n.content {\n    padding-top: 1.1rem;\n}\n\n.valid.modified:not([type=checkbox]) {\n    outline: 1px solid #26b050;\n}\n\n.centering {\n    float: none;\n    margin: 0 auto;\n}\n\n.invalid {\n    outline: 1px solid red;\n}\n\n.validation-message {\n    color: red;\n}\n\n#blazor-error-ui {\n    background: lightyellow;\n    bottom: 0;\n    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);\n    display: none;\n    left: 0;\n    padding: 0.6rem 1.25rem 0.7rem 1.25rem;\n    position: fixed;\n    width: 100%;\n    z-index: 1000;\n}\n\n    #blazor-error-ui .dismiss {\n        cursor: pointer;\n        position: absolute;\n        right: 0.75rem;\n        top: 0.5rem;\n    }\n\n.blazor-error-boundary {\n    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;\n    padding: 1rem 1rem 1rem 3.7rem;\n    color: white;\n}\n\n    .blazor-error-boundary::after {\n        content: \"An error has occurred.\"\n    }\n\n\n\n.mud-picker-color-content {\n    align-items: center;\n}\n"
  },
  {
    "path": "src/PresenceLight.Razor/wwwroot/js/site.js",
    "content": "﻿function saveAsFile(filename, bytesBase64) {\n    var link = document.createElement('a');\n    link.download = filename;\n    link.href = \"data:application/octet-stream;base64,\" + bytesBase64;\n    document.body.appendChild(link); // Needed for Firefox\n    link.click();\n    document.body.removeChild(link);\n}"
  },
  {
    "path": "src/PresenceLight.Web/.config/dotnet-tools.json",
    "content": "{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"microsoft.dotnet-msidentity\": {\n      \"version\": \"1.0.0-preview.2.21302.1\",\n      \"commands\": [\n        \"dotnet-msidentity\"\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/PresenceLight.Web/App.razor",
    "content": "﻿<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>PresenceLight</title>\n    <base href=\"/\" />\n    <link rel=\"stylesheet\" href=\"_content/PresenceLight.Razor/css/bootstrap/bootstrap.min.css\" />\n    <link rel=\"stylesheet\" href=\"_content/PresenceLight.Razor/css/bootstrap/bootstrap-big-grid.min.css\" />\n    <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.12.0/css/all.css\">\n    <link href=\"_content/PresenceLight.Razor/css/site.css\" rel=\"stylesheet\" />\n    <!-- inside of head section -->\n    <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css\" integrity=\"sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn\" crossorigin=\"anonymous\">\n    <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.15.4/css/all.css\">\n\n    <link href=\"_content/Blazorise/blazorise.css\" rel=\"stylesheet\" />\n    <link href=\"_content/Blazorise.Bootstrap/blazorise.bootstrap.css\" rel=\"stylesheet\" />\n    <link href=\"PresenceLight.styles.css\" rel=\"stylesheet\" />\n\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap\" rel=\"stylesheet\" />\n    <link href=\"_content/MudBlazor/MudBlazor.min.css\" rel=\"stylesheet\" />\n</head>\n<body>\n    <Routes @rendermode=\"@RenderMode.InteractiveServer\" />\n\n    <!-- inside of body section and after the div/app tag  -->\n    <script src=\"https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js\" integrity=\"sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj\" crossorigin=\"anonymous\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js\" integrity=\"sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN\" crossorigin=\"anonymous\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js\" integrity=\"sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2\" crossorigin=\"anonymous\"></script>\n\n\n    <link href=\"_content/BlazorPro.Spinkit/spinkit.min.css\" rel=\"stylesheet\" />\n\n    <div id=\"blazor-error-ui\">\n        An unhandled error has occurred.\n        <a href=\"\" class=\"reload\">Reload</a>\n        <a class=\"dismiss\">🗙</a>\n    </div>\n    <script src=\"_framework/blazor.web.js\"></script>\n\n    <script src=\"_content/PresenceLight.Razor/js/site.js\"></script>\n    <script src=\"_content/MudBlazor/MudBlazor.min.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/PresenceLight.Web/AppOld.razor",
    "content": "﻿@using Microsoft.AspNetCore.Components.Authorization\n@using PresenceLight.Razor.Components\n@using PresenceLight.Razor.Components.Layout\n@using Microsoft.AspNetCore.Components.Routing\n<CascadingAuthenticationState>\n    <Router AppAssembly=\"@typeof(PresenceLightClientApp).Assembly\">\n        <Found Context=\"routeData\">\n            <AuthorizeRouteView RouteData=\"@routeData\" DefaultLayout=\"@typeof(MainLayout)\" />\n            <FocusOnNavigate RouteData=\"@routeData\" Selector=\"h1\" />\n        </Found>\n        <NotFound>\n            <LayoutView Layout=\"@typeof(MainLayout)\">\n                <p role=\"alert\">Sorry, there's nothing at this address.</p>\n            </LayoutView>\n        </NotFound>\n    </Router>\n</CascadingAuthenticationState>"
  },
  {
    "path": "src/PresenceLight.Web/Dockerfile",
    "content": "#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.\n\nFROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base\nWORKDIR /app\n\nFROM mcr.microsoft.com/dotnet/sdk:9.0 AS build\nWORKDIR /src\nCOPY [\"PresenceLight.Web/PresenceLight.Web.csproj\", \"PresenceLight.Web/\"]\nCOPY [\"PresenceLight.Razor/PresenceLight.Razor.csproj\", \"PresenceLight.Razor/\"]\nCOPY [\"PresenceLight.Core/PresenceLight.Core.csproj\", \"PresenceLight.Core/\"]\nRUN dotnet restore \"PresenceLight.Web/PresenceLight.Web.csproj\"\nCOPY . .\nWORKDIR \"/src/PresenceLight.Web\"\nRUN dotnet build \"PresenceLight.Web.csproj\" -c Release -o /app/build\n\nFROM build AS publish\nRUN dotnet publish \"PresenceLight.Web.csproj\" -c Release -o /app/publish\n\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app/publish .\nLABEL org.opencontainers.image.source=https://github.com/isaacrlevin/presencelight\nENTRYPOINT [\"dotnet\", \"PresenceLight.dll\"]"
  },
  {
    "path": "src/PresenceLight.Web/Dockerfile.debian-arm32",
    "content": "FROM mcr.microsoft.com/dotnet/aspnet:9.0.0-bookworm-slim-arm32v7 AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n\nFROM mcr.microsoft.com/dotnet/sdk:9.0 AS build\nWORKDIR /src\nCOPY [\"PresenceLight.Web/PresenceLight.Web.csproj\", \"PresenceLight.Web/\"]\nCOPY [\"PresenceLight.Razor/PresenceLight.Razor.csproj\", \"PresenceLight.Razor/\"]\nCOPY [\"PresenceLight.Core/PresenceLight.Core.csproj\", \"PresenceLight.Core/\"]\nRUN dotnet restore \"PresenceLight.Web/PresenceLight.Web.csproj\"\nCOPY . .\nWORKDIR \"/src/PresenceLight.Web\"\nRUN dotnet build \"PresenceLight.Web.csproj\" -c Release -o /app/build -r linux-arm --self-contained false /p:Version={VERSION}\n\nFROM build AS publish\nRUN dotnet publish \"PresenceLight.Web.csproj\" -c Release -o /app/publish -r linux-arm --self-contained false /p:Version={VERSION}\n\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app/publish .\nLABEL org.opencontainers.image.source=https://github.com/isaacrlevin/presencelight\nENTRYPOINT [\"dotnet\", \"PresenceLight.dll\"]"
  },
  {
    "path": "src/PresenceLight.Web/Dockerfile.debian-arm64",
    "content": "FROM mcr.microsoft.com/dotnet/aspnet:9.0.0-bookworm-slim-arm64v8 AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n\nFROM mcr.microsoft.com/dotnet/sdk:9.0 AS build\nWORKDIR /src\nCOPY [\"PresenceLight.Web/PresenceLight.Web.csproj\", \"PresenceLight.Web/\"]\nCOPY [\"PresenceLight.Razor/PresenceLight.Razor.csproj\", \"PresenceLight.Razor/\"]\nCOPY [\"PresenceLight.Core/PresenceLight.Core.csproj\", \"PresenceLight.Core/\"]\nRUN dotnet restore \"PresenceLight.Web/PresenceLight.Web.csproj\"\nCOPY . .\nWORKDIR \"/src/PresenceLight.Web\"\nRUN dotnet build \"PresenceLight.Web.csproj\" -c Release -o /app/build -r linux-arm64 --self-contained false /p:Version={VERSION}\n\nFROM build AS publish\nRUN dotnet publish \"PresenceLight.Web.csproj\" -c Release -o /app/publish -r linux-arm64 --self-contained false /p:Version={VERSION}\n\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app/publish .\nLABEL org.opencontainers.image.source=https://github.com/isaacrlevin/presencelight\nENTRYPOINT [\"dotnet\", \"PresenceLight.dll\"]"
  },
  {
    "path": "src/PresenceLight.Web/Pages/Error.cshtml",
    "content": "﻿@page\n@model PresenceLight.Web.Pages.ErrorModel\n\n<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />\n    <title>Error</title>\n    <link href=\"~/css/bootstrap/bootstrap.min.css\" rel=\"stylesheet\" />\n    <link href=\"~/css/app.css\" rel=\"stylesheet\" asp-append-version=\"true\" />\n</head>\n\n<body>\n    <div class=\"main\">\n        <div class=\"content px-4\">\n            <h1 class=\"text-danger\">Error.</h1>\n            <h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n            @if (Model.ShowRequestId)\n            {\n                <p>\n                    <strong>Request ID:</strong> <code>@Model.RequestId</code>\n                </p>\n            }\n\n            <h3>Development Mode</h3>\n            <p>\n                Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n            </p>\n            <p>\n                <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n                It can result in displaying sensitive information from exceptions to end users.\n                For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n                and restarting the app.\n            </p>\n        </div>\n    </div>\n</body>\n\n</html>\n"
  },
  {
    "path": "src/PresenceLight.Web/Pages/Error.cshtml.cs",
    "content": "﻿using Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Web.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    [IgnoreAntiforgeryToken]\n    public class ErrorModel : PageModel\n    {\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Web/Pages/_Host.cshtml",
    "content": "﻿@page \"/\"\n@using PresenceLight.Razor\n@using PresenceLight.Web\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n@{\n    Layout = null;\n}\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>PresenceLight</title>\n    <base href=\"~/\" />\n    <link rel=\"stylesheet\" href=\"_content/PresenceLight.Razor/css/bootstrap/bootstrap.min.css\" />\n    <link rel=\"stylesheet\" href=\"_content/PresenceLight.Razor/css/bootstrap/bootstrap-big-grid.min.css\" />\n    <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.12.0/css/all.css\">\n    <link href=\"_content/PresenceLight.Razor/css/site.css\" rel=\"stylesheet\" />\n    <!-- inside of head section -->\n    <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css\" integrity=\"sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn\" crossorigin=\"anonymous\">\n    <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.15.4/css/all.css\">\n\n    <link href=\"_content/Blazorise/blazorise.css\" rel=\"stylesheet\" />\n    <link href=\"_content/Blazorise.Bootstrap/blazorise.bootstrap.css\" rel=\"stylesheet\" />\n    <link href=\"PresenceLight.styles.css\" rel=\"stylesheet\" />\n\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap\" rel=\"stylesheet\" />\n    <link href=\"_content/MudBlazor/MudBlazor.min.css\" rel=\"stylesheet\" />\n</head>\n<body>\n    <component type=\"typeof(AppOld)\" render-mode=\"ServerPrerendered\" />\n\n    <!-- inside of body section and after the div/app tag  -->\n    <script src=\"https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js\" integrity=\"sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj\" crossorigin=\"anonymous\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js\" integrity=\"sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN\" crossorigin=\"anonymous\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js\" integrity=\"sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2\" crossorigin=\"anonymous\"></script>\n\n\n    <link href=\"_content/BlazorPro.Spinkit/spinkit.min.css\" rel=\"stylesheet\" />\n\n    <div id=\"blazor-error-ui\">\n        <environment include=\"Staging,Production\">\n            An error has occurred. This application may no longer respond until reloaded.\n        </environment>\n        <environment include=\"Development\">\n            An unhandled exception has occurred. See browser dev tools for details.\n        </environment>\n        <a href=\"\" class=\"reload\">Reload</a>\n        <a class=\"dismiss\">🗙</a>\n    </div>\n\n    <script src=\"_framework/blazor.server.js\"></script>\n\n    <div id=\"blazor-error-ui\">\n        <environment include=\"Staging,Production\">\n            An error has occurred. This application may no longer respond until reloaded.\n        </environment>\n        <environment include=\"Development\">\n            An unhandled exception has occurred. See browser dev tools for details.\n        </environment>\n        <a href=\"\" class=\"reload\">Reload</a>\n        <a class=\"dismiss\">🗙</a>\n    </div>\n\n    <script src=\"_content/PresenceLight.Razor/js/site.js\"></script>\n    <script src=\"_content/MudBlazor/MudBlazor.min.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/PresenceLight.Web/PresenceLight.Web.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <LangVersion>latest</LangVersion>\n    <AssemblyName>PresenceLight</AssemblyName>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <ApplicationIcon>wwwroot\\favicon.ico</ApplicationIcon>\n    <UserSecretsId>8509488430545</UserSecretsId>\n    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>\n    <DockerfileFile>Dockerfile</DockerfileFile>\n    <DockerfileContext>..</DockerfileContext>\n    <DockerDevelopmentMode>Regular</DockerDevelopmentMode>\n    <DockerComposeProjectPath>..\\DockerCompose\\docker-compose.dcproj</DockerComposeProjectPath>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Content Remove=\"wwwroot\\favicon.ico\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Include=\"wwwroot\\favicon.ico\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.Authentication.JwtBearer\" Version=\"10.0.0\" NoWarn=\"NU1605\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Authentication.OpenIdConnect\" Version=\"10.0.0\" NoWarn=\"NU1605\" />\n    <PackageReference Include=\"Microsoft.Identity.Web\" Version=\"4.1.0\" />\n    <PackageReference Include=\"Microsoft.Identity.Web.UI\" Version=\"4.1.0\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Azure.Containers.Tools.Targets\" Version=\"1.22.1\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\PresenceLight.Core\\PresenceLight.Core.csproj\" />\n    <ProjectReference Include=\"..\\PresenceLight.Razor\\PresenceLight.Razor.csproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Content Update=\"appsettings.json\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n    <Content Update=\"PresenceLightSettings.Development.json\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Content>\n    <Content Update=\"PresenceLightSettings.json\">\n      <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n    </Content>\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "src/PresenceLight.Web/PresenceLightSettings.json",
    "content": "{\n  \"IconType\": \"\",\n  \"LightSettings\": {\n    \"HoursPassedStatus\": \"Keep\",\n    \"SyncLights\": true,\n    \"WorkingDays\": \"Monday|Tuesday|Wednesday|Thursday|Friday\",\n    \"WorkingHoursStartTime\": \"\",\n    \"WorkingHoursEndTime\": \"\",\n    \"UseAmPm\": true,\n    \"UseWorkingHours\": false,\n    \"PollingInterval\": 5.0,\n    \"UseDefaultBrightness\": true,\n    \"DefaultBrightness\": 100,\n    \"LIFX\": {\n      \"LIFXClientId\": \"\",\n      \"LIFXClientSecret\": \"\",\n      \"LIFXApiKey\": \"\",\n      \"IsEnabled\": false,\n      \"SelectedItemId\": \"\",\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"Statuses\": {\n        \"AvailabilityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#00ff55\"\n        },\n        \"AvailabilityAvailableIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF3300\"\n        },\n        \"AvailabilityBusyIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#B03CDE\"\n        },\n        \"AvailabilityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#4f824f\"\n        },\n        \"ActivityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityInACallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityInAConferenceCallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff00d4\"\n        },\n        \"ActivityInactiveStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityInAMeetingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOffWorkStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOutOfOfficeStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ae00ff\"\n        },\n        \"ActivityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityPresentingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityUrgentInterruptionsOnlyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#560061\"\n        },\n        \"ActivityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        }\n      }\n    },\n    \"Hue\": {\n      \"HueApiKey\": \"\",\n      \"SelectedItemId\": \"\",\n      \"HueIpAddress\": \"\",\n      \"RemoteHueClientId\": \"\",\n      \"RemoteHueClientSecret\": \"\",\n      \"RemoteHueClientAppName\": \"\",\n      \"IsEnabled\": false,\n      \"RemoteBridgeId\": \"\",\n      \"UseRemoteApi\": false,\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"Statuses\": {\n        \"AvailabilityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#00ff55\"\n        },\n        \"AvailabilityAvailableIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF3300\"\n        },\n        \"AvailabilityBusyIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#B03CDE\"\n        },\n        \"AvailabilityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#4f824f\"\n        },\n        \"ActivityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityInACallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityInAConferenceCallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff00d4\"\n        },\n        \"ActivityInactiveStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityInAMeetingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOffWorkStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOutOfOfficeStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ae00ff\"\n        },\n        \"ActivityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityPresentingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityUrgentInterruptionsOnlyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#560061\"\n        },\n        \"ActivityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        }\n      }\n    },\n    \"Yeelight\": {\n      \"SelectedItemId\": \"\",\n      \"IsEnabled\": false,\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"Statuses\": {\n        \"AvailabilityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#00ff55\"\n        },\n        \"AvailabilityAvailableIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF3300\"\n        },\n        \"AvailabilityBusyIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#B03CDE\"\n        },\n        \"AvailabilityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#4f824f\"\n        },\n        \"ActivityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityInACallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityInAConferenceCallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff00d4\"\n        },\n        \"ActivityInactiveStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityInAMeetingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOffWorkStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOutOfOfficeStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ae00ff\"\n        },\n        \"ActivityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityPresentingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityUrgentInterruptionsOnlyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#560061\"\n        },\n        \"ActivityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        }\n      }\n    },\n    \"CustomApi\": {\n      \"IsEnabled\": false,\n      \"SelectedItemId\": \"\",\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"CustomApiAvailable\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiBusy\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiBeRightBack\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiAway\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiDoNotDisturb\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiOffline\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiOff\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityAvailable\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityInACall\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityInAConferenceCall\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityInAMeeting\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityPresenting\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityBusy\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityAway\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiAvailableIdle\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityBeRightBack\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityDoNotDisturb\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityIdle\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityOffline\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityOff\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiActivityOffWork\": {\n        \"Method\": \"\",\n        \"Uri\": \"\",\n        \"Body\": \"\"\n      },\n      \"CustomApiTimeout\": 100,\n      \"IgnoreCertificateErrors\": false,\n      \"UseBasicAuth\": false,\n      \"BasicAuthUserName\": \"\",\n      \"BasicAuthUserPassword\": \"\"\n    },\n    \"LocalSerialHost\": {\n      \"IsEnabled\": false,\n      \"SelectedItemId\": \"\",\n      \"Brightness\": 100,\n      \"UseActivityStatus\": false,\n      \"BaudRate\": \"\",\n      \"PortNumber\": \"\",\n      \"LocalSerialHostMainSetup\": {\n        \"BaudRate\": \"\",\n        \"LineEnding\": \"\",\n        \"Port\": \"\"\n      },\n      \"LocalSerialHostAvailable\": \"\",\n      \"LocalSerialHostBusy\": \"\",\n      \"LocalSerialHostBeRightBack\": \"\",\n      \"LocalSerialHostAway\": \"\",\n      \"LocalSerialHostDoNotDisturb\": \"\",\n      \"LocalSerialHostOffline\": \"\",\n      \"LocalSerialHostOff\": \"\",\n      \"LocalSerialHostActivityAvailable\": \"\",\n      \"LocalSerialHostActivityInACall\": \"\",\n      \"LocalSerialHostActivityInAConferenceCall\": \"\",\n      \"LocalSerialHostActivityInAMeeting\": \"\",\n      \"LocalSerialHostActivityPresenting\": \"\",\n      \"LocalSerialHostActivityBusy\": \"\",\n      \"LocalSerialHostActivityAway\": \"\",\n      \"LocalSerialHostAvailableIdle\": \"\",\n      \"LocalSerialHostActivityBeRightBack\": \"\",\n      \"LocalSerialHostActivityDoNotDisturb\": \"\",\n      \"LocalSerialHostActivityIdle\": \"\",\n      \"LocalSerialHostActivityOffline\": \"\",\n      \"LocalSerialHostActivityOff\": \"\"\n    },\n    \"Wiz\": {\n      \"SelectedItemId\": \"\",\n      \"Brightness\": 100,\n      \"IsEnabled\": false,\n      \"UseActivityStatus\": false,\n      \"Statuses\": {\n        \"AvailabilityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#00ff55\"\n        },\n        \"AvailabilityAvailableIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FF3300\"\n        },\n        \"AvailabilityBusyIdleStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFF00\"\n        },\n        \"AvailabilityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#B03CDE\"\n        },\n        \"AvailabilityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"AvailabilityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityAvailableStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#4f824f\"\n        },\n        \"ActivityAwayStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBeRightBackStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityBusyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityDoNotDisturbStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityInACallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityInAConferenceCallStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff00d4\"\n        },\n        \"ActivityInactiveStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ffff00\"\n        },\n        \"ActivityInAMeetingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ff0000\"\n        },\n        \"ActivityOfflineStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOffWorkStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityOutOfOfficeStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#ae00ff\"\n        },\n        \"ActivityPresenceUnknownStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        },\n        \"ActivityPresentingStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#960000\"\n        },\n        \"ActivityUrgentInterruptionsOnlyStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#560061\"\n        },\n        \"ActivityOffStatus\": {\n          \"Disabled\": false,\n          \"Color\": \"#FFFFFF\"\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "src/PresenceLight.Web/Program.cs",
    "content": "﻿using System.Diagnostics;\n\nusing Blazorise;\nusing Blazorise.Bootstrap;\nusing Blazorise.Icons.FontAwesome;\n\nusing Microsoft.ApplicationInsights.Extensibility;\nusing Microsoft.AspNetCore.Authentication.OpenIdConnect;\nusing Microsoft.AspNetCore.HttpOverrides;\nusing Microsoft.Extensions.Options;\nusing Microsoft.Identity.Web;\nusing Microsoft.Identity.Web.UI;\nusing Microsoft.IdentityModel.Protocols.OpenIdConnect;\nusing Microsoft.IdentityModel.Tokens;\n\nusing PresenceLight.Core;\nusing PresenceLight.Razor;\nusing PresenceLight.Razor.Components;\nusing PresenceLight.Web;\n\nusing Serilog;\n\n\n//var app = ProgramNew.GetWebApplication(args);\n\nvar app = ProgramOld.GetWebApplication(args);\n\napp.Run();\n"
  },
  {
    "path": "src/PresenceLight.Web/Program_New.cs",
    "content": "﻿using System.Diagnostics;\n\nusing Blazorise;\nusing Blazorise.Bootstrap;\nusing Blazorise.Icons.FontAwesome;\n\nusing Microsoft.ApplicationInsights.Extensibility;\nusing Microsoft.AspNetCore.Authentication.OpenIdConnect;\nusing Microsoft.AspNetCore.HttpOverrides;\nusing Microsoft.Identity.Web;\nusing Microsoft.Identity.Web.UI;\nusing Microsoft.IdentityModel.Protocols.OpenIdConnect;\nusing Microsoft.IdentityModel.Tokens;\n\nusing PresenceLight.Core;\n\nusing Serilog;\n\nnamespace PresenceLight.Web\n{\n\n\n    public static class ProgramNew\n    {\n        public static WebApplication GetWebApplication(string[] args)\n        {\n            var builder = WebApplication.CreateBuilder(args);\n\n            WebApplication app = null;\n\n\n            ConfigurationBuilder configBuilderForMain = new ConfigurationBuilder();\n\n            configBuilderForMain\n                .SetBasePath(Directory.GetCurrentDirectory())\n                .AddEnvironmentVariables(); ;\n\n            if (Debugger.IsAttached)\n            {\n                configBuilderForMain.AddJsonFile(\"appsettings.Development.json\", optional: true, reloadOnChange: false);\n                configBuilderForMain.AddJsonFile(\"PresenceLightSettings.Development.json\", optional: true, reloadOnChange: false);\n            }\n            else\n            {\n                configBuilderForMain.AddJsonFile(\"PresenceLightSettings.json\", optional: false, reloadOnChange: false);\n                configBuilderForMain.AddJsonFile(\"appsettings.json\", optional: false, reloadOnChange: false);\n            }\n\n            if (Environment.GetEnvironmentVariable(\"DOTNET_RUNNING_IN_CONTAINER\") == \"true\")\n            {\n                configBuilderForMain.AddJsonFile(System.IO.Path.Combine(\"config\", \"appsettings.json\"), optional: true, reloadOnChange: false);\n                configBuilderForMain.AddJsonFile(System.IO.Path.Combine(\"config\", \"PresenceLightSettings.json\"), optional: true, reloadOnChange: false);\n            }\n\n\n            configBuilderForMain.Build();\n\n            IConfiguration configForMain = configBuilderForMain.Build();\n\n            var telemetryConfiguration = TelemetryConfiguration.CreateDefault();\n            telemetryConfiguration.InstrumentationKey = configForMain[\"ApplicationInsights:InstrumentationKey\"];\n\n            Log.Logger = new LoggerConfiguration()\n                 .ReadFrom.Configuration(configForMain)\n                 .WriteTo.PresenceEventsLogSink()\n                 .WriteTo.ApplicationInsights(telemetryConfiguration, TelemetryConverter.Traces, Serilog.Events.LogEventLevel.Error)\n                 .Enrich.FromLogContext()\n                 .CreateLogger();\n\n            builder.Configuration.SetBasePath(Directory.GetCurrentDirectory());\n\n            if (Debugger.IsAttached)\n            {\n                builder.Configuration.AddJsonFile(\"appsettings.Development.json\", optional: true, reloadOnChange: false);\n                builder.Configuration.AddJsonFile(\"PresenceLightSettings.Development.json\", optional: true, reloadOnChange: false);\n            }\n            else\n            {\n                builder.Configuration.AddJsonFile(\"PresenceLightSettings.json\", optional: false, reloadOnChange: false);\n                builder.Configuration.AddJsonFile(\"appsettings.json\", optional: false, reloadOnChange: false);\n            }\n\n            if (Environment.GetEnvironmentVariable(\"DOTNET_RUNNING_IN_CONTAINER\") == \"true\")\n            {\n                builder.Configuration.AddJsonFile(System.IO.Path.Combine(\"config\", \"appsettings.json\"), optional: true, reloadOnChange: false);\n                builder.Configuration.AddJsonFile(System.IO.Path.Combine(\"config\", \"PresenceLightSettings.json\"), optional: true, reloadOnChange: false);\n            }\n\n            builder.Configuration.AddEnvironmentVariables();\n\n\n            builder.Logging.AddSerilog();\n            builder.Host.UseSerilog();\n\n\n            var initialScopes = builder.Configuration.GetValue<string>(\"DownstreamApi:Scopes\")?.Split(' ');\n\n            builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)\n                .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(\"AADSettings\"))\n                    .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)\n                        .AddMicrosoftGraph(builder.Configuration.GetSection(\"DownstreamApi\"))\n                        .AddInMemoryTokenCaches();\n\n\n            builder.Services.AddControllersWithViews()\n                .AddMicrosoftIdentityUI();\n\n            builder.Services.AddOptions<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme)\n             .Configure<IServiceProvider>((options, serviceProvider) =>\n             {\n                 options.ResponseType = OpenIdConnectResponseType.Code;\n                 options.UsePkce = false;\n                 options.Authority = $\"{builder.Configuration[\"AADSettings:Instance\"]}common/v2.0\";\n\n                 options.Scope.Add(\"offline_access\");\n                 options.Scope.Add(\"User.Read\");\n\n                 options.TokenValidationParameters = new TokenValidationParameters\n                 {\n                     // Azure ID tokens give name in \"name\"\n                     NameClaimType = \"name\",\n                     ValidateIssuer = false\n                 };\n\n                 options.Events = new OpenIdConnectEvents\n                 {\n                     OnAuthenticationFailed = async context =>\n                     {\n                         context.Response.Redirect(\"/Error\");\n                         context.HandleResponse();\n                     },\n\n                     OnAuthorizationCodeReceived = async context =>\n                     {\n\n                         context.HandleCodeRedemption();\n\n\n                         var loginService = app.Services.GetRequiredService<LoginService>();\n\n                         var idToken = await loginService\n                               .AddUserToTokenCache(context.ProtocolMessage.Code);\n\n                         context.HandleCodeRedemption(null, idToken);\n                     },\n                     OnRedirectToIdentityProviderForSignOut = async context =>\n                     {\n                         var loginService = app.Services.GetRequiredService<LoginService>();\n                         await loginService.SignOut();\n                     }\n                 };\n             });\n\n\n            builder.Services.AddHostedService<Worker>();\n\n            builder.Services.AddAuthorization(options =>\n            {\n                // By default, all incoming requests will be authorized according to the default policy\n                options.FallbackPolicy = options.DefaultPolicy;\n            });\n\n            builder.Services.AddPresenceLight(builder.Configuration);\n            //builder.Services.AddRazorPages();\n            //builder.Services.AddServerSideBlazor()\n            builder.Services.AddRazorComponents()\n                .AddInteractiveServerComponents()\n                .AddMicrosoftIdentityConsentHandler();\n\n            builder.Services.AddCascadingAuthenticationState();\n\n            builder.Services\n                .AddBlazorise(options =>\n                {\n                    options.Immediate = true;\n                })\n                .AddBootstrapProviders()\n                .AddFontAwesomeIcons();\n\n            builder.Services.Configure<ForwardedHeadersOptions>(options =>\n            {\n                options.ForwardedHeaders =\n                    ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;\n            });\n\n            builder.Services.AddSingleton<AuthorizationProvider, AuthorizationProvider>();\n            builder.Services.AddSingleton<LoginService, LoginService>();\n\n            app = builder.Build();\n\n            app.UseForwardedHeaders();\n\n            if (app.Environment.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n\n            app.UseRouting();\n            app.UseAntiforgery();\n\n            app.UseAuthentication();\n            app.UseAuthorization();\n\n            //app.UseEndpoints(endpoints =>\n            //{\n            //    endpoints.MapControllers();\n            //    endpoints.MapBlazorHub();\n            //    endpoints.MapFallbackToPage(\"/_Host\");\n            //});\n\n            app.MapRazorComponents<App>()\n                .AddInteractiveServerRenderMode();\n\n            return app;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Web/Program_Old.cs",
    "content": "﻿using System.Diagnostics;\n\nusing Blazorise;\nusing Blazorise.Bootstrap;\nusing Blazorise.Icons.FontAwesome;\n\nusing Microsoft.ApplicationInsights.Extensibility;\nusing Microsoft.AspNetCore.Authentication.OpenIdConnect;\nusing Microsoft.AspNetCore.HttpOverrides;\nusing Microsoft.Extensions.Options;\nusing Microsoft.Identity.Web;\nusing Microsoft.Identity.Web.UI;\nusing Microsoft.IdentityModel.Protocols.OpenIdConnect;\nusing Microsoft.IdentityModel.Tokens;\n\nusing PresenceLight.Core;\nusing PresenceLight.Razor;\nusing PresenceLight.Web;\n\nusing Serilog;\n\nnamespace PresenceLight.Web {\n\n\n    public static class ProgramOld\n    {\n        public static WebApplication GetWebApplication(string[] args) {\n\n            var builder = WebApplication.CreateBuilder(args);\n\n            WebApplication app = null;\n\n\n            ConfigurationBuilder configBuilderForMain = new ConfigurationBuilder();\n\n            configBuilderForMain\n                .SetBasePath(Directory.GetCurrentDirectory())\n                .AddEnvironmentVariables(); ;\n\n            if (Debugger.IsAttached)\n            {\n                configBuilderForMain.AddJsonFile(\"appsettings.Development.json\", optional: true, reloadOnChange: false);\n                configBuilderForMain.AddJsonFile(\"PresenceLightSettings.Development.json\", optional: true, reloadOnChange: false);\n            }\n            else\n            {\n                configBuilderForMain.AddJsonFile(\"PresenceLightSettings.json\", optional: false, reloadOnChange: false);\n                configBuilderForMain.AddJsonFile(\"appsettings.json\", optional: false, reloadOnChange: false);\n            }\n\n            if (Environment.GetEnvironmentVariable(\"DOTNET_RUNNING_IN_CONTAINER\") == \"true\")\n            {\n                configBuilderForMain.AddJsonFile(System.IO.Path.Combine(\"config\", \"appsettings.json\"), optional: true, reloadOnChange: false);\n                configBuilderForMain.AddJsonFile(System.IO.Path.Combine(\"config\", \"PresenceLightSettings.json\"), optional: true, reloadOnChange: false);\n            }\n\n\n            configBuilderForMain.Build();\n\n            IConfiguration configForMain = configBuilderForMain.Build();\n\n            var telemetryConfiguration = TelemetryConfiguration.CreateDefault();\n            telemetryConfiguration.InstrumentationKey = configForMain[\"ApplicationInsights:InstrumentationKey\"];\n\n            Log.Logger = new LoggerConfiguration()\n                 .ReadFrom.Configuration(configForMain)\n                 .WriteTo.PresenceEventsLogSink()\n                 .WriteTo.ApplicationInsights(telemetryConfiguration, TelemetryConverter.Traces, Serilog.Events.LogEventLevel.Error)\n                 .Enrich.FromLogContext()\n                 .CreateLogger();\n\n            builder.Configuration.SetBasePath(Directory.GetCurrentDirectory());\n\n            if (Debugger.IsAttached)\n            {\n                builder.Configuration.AddJsonFile(\"appsettings.Development.json\", optional: true, reloadOnChange: false);\n                builder.Configuration.AddJsonFile(\"PresenceLightSettings.Development.json\", optional: true, reloadOnChange: false);\n            }\n            else\n            {\n                builder.Configuration.AddJsonFile(\"PresenceLightSettings.json\", optional: false, reloadOnChange: false);\n                builder.Configuration.AddJsonFile(\"appsettings.json\", optional: false, reloadOnChange: false);\n            }\n\n            if (Environment.GetEnvironmentVariable(\"DOTNET_RUNNING_IN_CONTAINER\") == \"true\")\n            {\n                builder.Configuration.AddJsonFile(System.IO.Path.Combine(\"config\", \"appsettings.json\"), optional: true, reloadOnChange: false);\n                builder.Configuration.AddJsonFile(System.IO.Path.Combine(\"config\", \"PresenceLightSettings.json\"), optional: true, reloadOnChange: false);\n            }\n\n            builder.Configuration.AddEnvironmentVariables();\n\n\n            builder.Logging.AddSerilog();\n            builder.Host.UseSerilog();\n\n\n            var initialScopes = builder.Configuration.GetValue<string>(\"DownstreamApi:Scopes\")?.Split(' ');\n\n            builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)\n                .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(\"AADSettings\"))\n                    .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)\n                        .AddMicrosoftGraph(builder.Configuration.GetSection(\"DownstreamApi\"))\n                        .AddInMemoryTokenCaches();\n\n\n            builder.Services.AddControllersWithViews()\n                .AddMicrosoftIdentityUI();\n\n            builder.Services.AddOptions<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme)\n             .Configure<IServiceProvider>((options, serviceProvider) =>\n             {\n                 options.ResponseType = OpenIdConnectResponseType.Code;\n                 options.UsePkce = false;\n                 options.Authority = $\"{builder.Configuration[\"AADSettings:Instance\"]}common/v2.0\";\n\n                 options.Scope.Add(\"offline_access\");\n                 options.Scope.Add(\"User.Read\");\n\n                 options.TokenValidationParameters = new TokenValidationParameters\n                 {\n                     // Azure ID tokens give name in \"name\"\n                     NameClaimType = \"name\",\n                     ValidateIssuer = false\n                 };\n\n                 options.Events = new OpenIdConnectEvents\n                 {\n                     OnAuthenticationFailed = async context =>\n                     {\n                         context.Response.Redirect(\"/Error\");\n                         context.HandleResponse();\n                     },\n\n                     OnAuthorizationCodeReceived = async context =>\n                     {\n\n                         context.HandleCodeRedemption();\n\n\n                         var loginService = app.Services.GetRequiredService<LoginService>();\n\n                         var idToken = await loginService\n                               .AddUserToTokenCache(context.ProtocolMessage.Code);\n\n                         context.HandleCodeRedemption(null, idToken);\n                     },\n                     OnRedirectToIdentityProviderForSignOut = async context =>\n                     {\n                         var loginService = app.Services.GetRequiredService<LoginService>();\n                         await loginService.SignOut();\n                     }\n                 };\n             });\n\n\n            builder.Services.AddHostedService<Worker>();\n\n            builder.Services.AddAuthorization(options =>\n            {\n                // By default, all incoming requests will be authorized according to the default policy\n                options.FallbackPolicy = options.DefaultPolicy;\n            });\n\n            builder.Services.AddPresenceLight(builder.Configuration);\n            builder.Services.AddRazorPages();\n            builder.Services.AddServerSideBlazor()\n                .AddMicrosoftIdentityConsentHandler();\n\n            builder.Services\n                .AddBlazorise(options =>\n                {\n                    options.Immediate = true;\n                })\n                .AddBootstrapProviders()\n                .AddFontAwesomeIcons();\n\n            builder.Services.Configure<ForwardedHeadersOptions>(options =>\n            {\n                options.ForwardedHeaders =\n                    ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;\n            });\n\n            builder.Services.AddSingleton<AuthorizationProvider, AuthorizationProvider>();\n            builder.Services.AddSingleton<LoginService, LoginService>();\n\n            app = builder.Build();\n\n            app.UseForwardedHeaders();\n\n            if (app.Environment.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n\n            app.UseRouting();\n            app.UseAntiforgery();\n\n            app.UseAuthentication();\n            app.UseAuthorization();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllers();\n                endpoints.MapBlazorHub();\n                endpoints.MapFallbackToPage(\"/_Host\");\n            });\n\n            return app;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Web/Properties/PublishProfiles/FolderProfile.pubxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nhttps://go.microsoft.com/fwlink/?LinkID=208121. \n-->\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <DeleteExistingFiles>False</DeleteExistingFiles>\n    <ExcludeApp_Data>False</ExcludeApp_Data>\n    <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>\n    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>\n    <LastUsedPlatform>Any CPU</LastUsedPlatform>\n    <PublishProvider>FileSystem</PublishProvider>\n    <PublishUrl>bin\\Release\\net10.0\\publish\\</PublishUrl>\n    <WebPublishMethod>FileSystem</WebPublishMethod>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "src/PresenceLight.Web/Properties/launchSettings.json",
    "content": "{\n  \"iisSettings\": {\n    \"windowsAuthentication\": false,\n    \"anonymousAuthentication\": true,\n    \"iisExpress\": {\n      \"applicationUrl\": \"http://localhost:28919\",\n      \"sslPort\": 44342\n    }\n  },\n  \"profiles\": {\n    \"PresenceLight.Web\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      },\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\",\n      \"dotnetRunMessages\": true\n    },\n    \"IIS Express\": {\n      \"commandName\": \"IISExpress\",\n      \"launchBrowser\": true,\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    },\n    \"Docker\": {\n      \"commandName\": \"Docker\",\n      \"launchBrowser\": false,\n      \"launchUrl\": \"{Scheme}://{ServiceHost}:{ServicePort}\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_URLS\": \"https://+:443;http://+:80\",\n        \"ASPNETCORE_HTTPS_PORT\": \"5001\"\n      },\n      \"httpPort\": 5000,\n      \"useSSL\": true,\n      \"sslPort\": 5001\n    }\n  }\n}"
  },
  {
    "path": "src/PresenceLight.Web/Properties/serviceDependencies.json",
    "content": "{\n  \"dependencies\": {\n    \"secrets1\": {\n      \"type\": \"secrets\"\n    },\n    \"identityapp.aad1\": {\n      \"type\": \"identityapp.aad\",\n      \"connectionId\": \"AzureAD:ClientSecret\"\n    }\n  }\n}"
  },
  {
    "path": "src/PresenceLight.Web/Properties/serviceDependencies.local.json",
    "content": "{\n  \"dependencies\": {\n    \"secrets1\": {\n      \"type\": \"secrets.user\"\n    },\n    \"identityapp.aad1\": {\n      \"type\": \"identityapp.aad.callsgraph\",\n      \"connectionId\": \"AzureAD:ClientSecret\",\n      \"secretStore\": \"LocalSecretsFile\"\n    }\n  }\n}"
  },
  {
    "path": "src/PresenceLight.Web/Routes.razor",
    "content": "﻿<Router AppAssembly=\"@typeof(Program).Assembly\"\n        AdditionalAssemblies=\"new[] { typeof(PresenceLightClientApp).Assembly }\">\n    <Found Context=\"routeData\">\n        <AuthorizeRouteView RouteData=\"@routeData\" DefaultLayout=\"@typeof(MainLayout)\" />\n        <FocusOnNavigate RouteData=\"@routeData\" Selector=\"h1\" />\n    </Found>\n</Router>"
  },
  {
    "path": "src/PresenceLight.Web/ServiceCollectionExtensions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nusing MediatR;\n\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\n\nusing MudBlazor.Services;\n\nusing PresenceLight.Core;\nusing PresenceLight.Razor;\nusing PresenceLight.Razor.Services;\nusing PresenceLight.Razor.Components;\nnamespace PresenceLight.Web\n{\n    public static class ServiceCollectionExtensions\n    {\n        public static IServiceCollection AddPresenceLight(this IServiceCollection services, IConfiguration Configuration)\n        {\n            services.AddMediatR(cfg =>\n            {\n                cfg.RegisterServicesFromAssembly(typeof(PresenceLightClientApp).Assembly);\n                cfg.RegisterServicesFromAssembly(typeof(BaseConfig).Assembly);\n            });\n\n            services.AddMudServices();\n\n            services.AddHttpClient();\n\n            services.AddHttpContextAccessor();\n\n            services.Configure<BaseConfig>(Configuration);\n            services.AddSingleton<ISettingsService, WebAppSettingsService>();\n\n            services.AddOptions();\n            services.AddSingleton<AppState>();\n            services.AddSingleton<AppInfo, AppInfo>();\n            services.AddPresenceServices();\n\n            return services;\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Web/Worker.cs",
    "content": "﻿using System.Text.RegularExpressions;\n\nusing Microsoft.Extensions.Options;\nusing Microsoft.Graph;\nusing Microsoft.Graph.Models;\n\nusing PresenceLight.Core;\n\nnamespace PresenceLight.Web\n{\n    public class Worker : BackgroundService\n    {\n        private readonly AppState _appState;\n        private readonly ILogger<Worker> _logger;\n        LoginService loginService;\n        \n        private MediatR.IMediator _mediator;\n\n        public Worker(ILogger<Worker> logger,\n                      IOptionsMonitor<BaseConfig> optionsAccessor,\n                      AppState appState,\n                      LoginService _loginService,\n                      MediatR.IMediator mediator)\n        {\n            _mediator = mediator;\n            loginService = _loginService;\n            _logger = logger;\n            _appState = appState;\n            _appState.Config = optionsAccessor.CurrentValue;\n        }\n\n        protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n        {\n            while (!stoppingToken.IsCancellationRequested)\n            {\n                if (await loginService.IsUserAuthenticated())\n                {\n                    _logger.LogInformation(\"User is Authenticated, starting worker\");\n                    try\n                    {\n                        await Run();\n                    }\n                    catch (Exception e)\n                    {\n                        _logger.LogError(e, \"Exception occurred restarting worker\");\n                    }\n                }\n                else\n                {\n                    _logger.LogInformation(\"User is Not Authenticated, restarting worker\");\n                }\n                await Task.Delay(1000, stoppingToken);\n            }\n        }\n\n\n        private async Task Run()\n        {\n\n            try\n            {\n                if (!await _mediator.Send(new Core.GraphServices.GetIsInitializedCommand()))\n                {\n                    await _mediator.Send(new Core.GraphServices.InitializeCommand()\n                    {\n                    });\n\n                    if (loginService.IsInitialized)\n                    {\n                        _appState.SignedIn = true;\n                    }\n                }\n\n                var (user, presence) = await GetUserAndPresence();\n                var photo = await GetPhotoAsBase64Async();\n                \n                //Attach properties to all logging within this context..\n                using (Serilog.Context.LogContext.PushProperty(\"Availability\", presence.Availability))\n                using (Serilog.Context.LogContext.PushProperty(\"Activity\", presence.Activity))\n                {\n                    _appState.SetUserInfo(user, presence, photo);\n\n                    await SetColor(_appState.Presence.Availability, _appState.Presence.Activity);\n                    await InteractWithLights();\n                }\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Exception occurred in running worker\");\n                throw;\n            }\n        }\n\n        private async Task InteractWithLights()\n        {\n            bool previousWorkingHours = false;\n            while (await loginService.IsUserAuthenticated())\n            {\n\n                bool useWorkingHours = await _mediator.Send(new Core.WorkingHoursServices.UseWorkingHoursCommand());\n                bool IsInWorkingHours = await _mediator.Send(new Core.WorkingHoursServices.IsInWorkingHoursCommand());\n\n                try\n                {\n                    await Task.Delay(Convert.ToInt32(_appState.Config.LightSettings.PollingInterval * 1000));\n\n                    bool touchLight = false;\n                    string newColor = \"\";\n\n                    if (_appState.Config.LightSettings.SyncLights)\n                    {\n                        if (!useWorkingHours)\n                        {\n                            if (_appState.LightMode == \"Graph\")\n                            {\n                                touchLight = true;\n                            }\n                        }\n                        else\n                        {\n                            if (IsInWorkingHours)\n                            {\n                                previousWorkingHours = IsInWorkingHours;\n                                if (_appState.LightMode == \"Graph\")\n                                {\n                                    touchLight = true;\n                                }\n                            }\n                            else\n                            {\n                                // check to see if working hours have passed\n                                if (previousWorkingHours)\n                                {\n                                    switch (_appState.Config.LightSettings.HoursPassedStatus)\n                                    {\n                                        case \"Keep\":\n                                            break;\n                                        case \"White\":\n                                            newColor = \"Offline\";\n                                            break;\n                                        case \"Off\":\n                                            newColor = \"Off\";\n                                            break;\n                                        default:\n                                            break;\n                                    }\n                                    touchLight = true;\n                                }\n                            }\n                        }\n                    }\n\n                    if (touchLight)\n                    {\n                        switch (_appState.LightMode)\n                        {\n                            case \"Graph\":\n                                _logger.LogInformation(\"PresenceLight Running in Teams Mode\");\n                                _appState.Presence = await System.Threading.Tasks.Task.Run(() => GetPresence());\n\n                                if (newColor == string.Empty)\n                                {\n                                    await SetColor(_appState.Presence.Availability, _appState.Presence.Activity);\n                                }\n                                else\n                                {\n                                    await SetColor(newColor, newColor);\n                                }\n                                break;\n                            default:\n                                break;\n                        }\n                    }\n                }\n                catch (Exception e)\n                {\n                    _logger.LogError(e, \"Error Occurred Interacting with Lights\");\n                }\n            }\n        }\n\n        private async Task<(User, Presence)> GetUserAndPresence()\n        {\n            try\n            {\n                var (profile, presence) = await _mediator.Send(new Core.GraphServices.GetProfileAndPresenceCommand());\n\n                _logger.LogInformation($\"User is {profile.DisplayName}\");\n                return (profile, presence);\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Exception getting me\");\n                throw;\n            }\n        }\n\n        private async Task<string> GetPhotoAsBase64Async()\n        {\n            try\n            {\n                var photoStream = await _mediator.Send(new Core.GraphServices.GetPhotoCommand());\n                var memoryStream = new MemoryStream();\n                photoStream.CopyTo(memoryStream);\n\n                var photoBytes = memoryStream.ToArray();\n                var base64Photo = $\"data:image/gif;base64,{Convert.ToBase64String(photoBytes)}\";\n\n                return base64Photo;\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Exception getting photo\");\n                return null;\n            }\n        }\n\n        public async Task<Presence> GetPresence()\n        {\n            try\n            {\n                return await _mediator.Send(new Core.GraphServices.GetPresenceCommand());\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Error occurred Getting Presence\");\n                throw;\n            }\n        }\n\n        private async Task SetColor(string color, string activity = null)\n        {\n            try\n            {\n                if (_appState.Config.LightSettings.Hue.IsEnabled)\n                {\n                    if (Helpers.AreStringsNotEmpty(new string[] {_appState.Config.LightSettings.Hue.HueApiKey,\n                                                    _appState.Config.LightSettings.Hue.HueIpAddress,\n                                                    _appState.Config.LightSettings.Hue.SelectedItemId }))\n                    {\n                        if (_appState.Config.LightSettings.Hue.UseRemoteApi)\n                        {\n                            if (!string.IsNullOrEmpty(_appState.Config.LightSettings.Hue.RemoteBridgeId))\n                            {\n                                await _mediator.Send(new Core.RemoteHueServices.SetColorCommand\n                                {\n                                    Availability = color,\n                                    Activity = activity,\n                                    LightId = _appState.Config.LightSettings.Hue.SelectedItemId,\n                                    BridgeId = _appState.Config.LightSettings.Hue.RemoteBridgeId\n                                });\n                            }\n                        }\n                        else\n                        {\n                            await _mediator.Send(new Core.HueServices.SetColorCommand() { Activity = activity, Availability = color, LightID = _appState.Config.LightSettings.Hue.SelectedItemId });\n\n                        }\n                    }\n                }\n\n                if (_appState.Config.LightSettings.LIFX.IsEnabled && !string.IsNullOrEmpty(_appState.Config.LightSettings.LIFX.LIFXApiKey))\n                {\n                    await _mediator.Send(new Core.LifxServices.SetColorCommand() { Availability = color, Activity = activity, LightId = _appState.Config.LightSettings.LIFX.SelectedItemId });\n                }\n\n                if (_appState.Config.LightSettings.Yeelight.IsEnabled && !string.IsNullOrEmpty(_appState.Config.LightSettings.Yeelight.SelectedItemId))\n                {\n                    await _mediator.Send(new PresenceLight.Core.YeelightServices.SetColorCommand { Activity = activity, Availability = color, LightId = _appState.Config.LightSettings.Yeelight.SelectedItemId });\n                }\n\n                if (_appState.Config.LightSettings.CustomApi.IsEnabled)\n                {\n                    string response = await _mediator.Send(new Core.CustomApiServices.SetColorCommand\n                    {\n                        Activity = activity,\n                        Availability = color\n                    });\n                }\n\n                if (_appState.Config.LightSettings.LocalSerialHost.IsEnabled && !string.IsNullOrEmpty(_appState.Config.LightSettings.LocalSerialHost.SelectedItemId))\n                {\n                    string response = await _mediator.Send(new Core.LocalSerialHostServices.SetColorCommand\n                    {\n                        Activity = activity,\n                        Availability = color\n                    });\n                }\n\n                if (_appState.Config.LightSettings.Wiz.IsEnabled)\n                {\n                    //await _mediator.Send(new Core.WizServices.SetColorCommand\n                    //{\n                    //    Activity = activity,\n                    //    Availability = color,\n                    //    LightID = _appState.Config.LightSettings.Wiz.SelectedItemId\n                    //});\n                }\n            }\n            catch (Exception e)\n            {\n                _logger.LogError(e, \"Exception setting color\");\n                throw;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/PresenceLight.Web/_Imports.razor",
    "content": "﻿@using Microsoft.AspNetCore.Components.Authorization\n@using PresenceLight.Razor.Components\n@using PresenceLight.Razor.Components.Layout\n@using Microsoft.AspNetCore.Components.Routing\n@using Microsoft.AspNetCore.Components.Web"
  },
  {
    "path": "src/PresenceLight.Web/appsettings.json",
    "content": "{\n  \"AADSettings\": {\n    \"Instance\": \"https://login.microsoftonline.com/\",\n    \"TenantId\": \"\",\n    \"ClientId\": \"\",\n    \"RedirectHost\": \"https://localhost:5001\",\n    \"CallbackPath\": \"/signin-oidc\",\n    \"SignedOutCallbackPath \": \"/signout-callback-oidc\",\n    \"ClientSecret\": \"\",\n    \"Scopes\": [\n      \"https://graph.microsoft.com/.default\"\n    ]\n  },\n  \"DownstreamApi\": {\n    \"BaseUrl\": \"https://graph.microsoft.com/beta\",\n    \"Scopes\": \"user.read presence.read offline_access\"\n  },\n  \"ApplicationInsights\": {\n    \"InstrumentationKey\": \"\"\n  },\n  \"SnapshotCollectorConfiguration\": {\n    \"IsEnabledInDeveloperMode\": true,\n    \"ThresholdForSnapshotting\": 1,\n    \"MaximumSnapshotsRequired\": 3,\n    \"MaximumCollectionPlanSize\": 50,\n    \"ReconnectInterval\": \"00:15:00\",\n    \"ProblemCounterResetInterval\": \"1.00:00:00\",\n    \"SnapshotsPerTenMinutesLimit\": 1,\n    \"SnapshotsPerDayLimit\": 30,\n    \"SnapshotInLowPriorityThread\": true,\n    \"ProvideAnonymousTelemetry\": true,\n    \"FailedRequestLimit\": 3\n  },\n  \"Serilog\": {\n    \"Using\": [ \"Serilog.Sinks.Console\", \"Serilog.Sinks.File\" ],\n    \"MinimumLevel\": \"Information\",\n    \"WriteTo\": [\n      { \"Name\": \"Console\" },\n      {\n        \"Name\": \"File\",\n        \"Args\": {\n          \"path\": \"config/logs/log-.json\",\n          \"formatter\": \"Serilog.Formatting.Json.JsonFormatter, Serilog\",\n          \"rollingInterval\": \"Hour\",\n          \"shared\": \"true\",\n          \"retainedFileCountLimit\": 24\n        }\n      }\n    ],\n    \"Enrich\": [ \"FromLogContext\", \"WithThreadId\" ],\n    \"Properties\": {\n      \"Application\": \"PresenceLight\"\n    }\n  },\n  \"AppType\": \"Web\",\n  \"AppVersion\": \"\"\n}\n"
  },
  {
    "path": "src/PresenceLight.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.0.31717.71\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"PresenceLight.Core\", \"PresenceLight.Core\\PresenceLight.Core.csproj\", \"{4883809C-FF4B-4504-A85E-2503607E5B99}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"PresenceLight\", \"DesktopClient\\PresenceLight\\PresenceLight.csproj\", \"{E0C9DB61-A1E6-4254-A200-A946F4353D66}\"\nEndProject\nProject(\"{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}\") = \"PresenceLight.Package\", \"DesktopClient\\PresenceLight.Package\\PresenceLight.Package.wapproj\", \"{12DDAD24-CCBD-409F-9342-17A0E445604F}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{9BCEC868-73D3-4393-8099-D4DF2DC6DB74}\"\n\tProjectSection(SolutionItems) = preProject\n\t\t..\\.gitignore = ..\\.gitignore\n\tEndProjectSection\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Readmes\", \"Readmes\", \"{AEA7D3CB-0200-4256-BB51-D9A63E788C97}\"\n\tProjectSection(SolutionItems) = preProject\n\t\t..\\docs\\desktop-README.md = ..\\docs\\desktop-README.md\n\t\t..\\README.md = ..\\README.md\n\t\t..\\docs\\web-README.md = ..\\docs\\web-README.md\n\tEndProjectSection\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"PresenceLight.Razor\", \"PresenceLight.Razor\\PresenceLight.Razor.csproj\", \"{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"PresenceLight.Web\", \"PresenceLight.Web\\PresenceLight.Web.csproj\", \"{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}\"\nEndProject\nProject(\"{E53339B2-1760-4266-BCC7-CA923CBCF16C}\") = \"docker-compose\", \"DockerCompose\\docker-compose.dcproj\", \"{D6871E74-A6E2-41EE-AA4A-7BE357501D63}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"GitHub Workflows\", \"GitHub Workflows\", \"{E1FC1E76-1B1A-411B-B5A1-42446709144B}\"\n\tProjectSection(SolutionItems) = preProject\n\t\t..\\.github\\workflows\\Azure_Blob_Deploy.yml = ..\\.github\\workflows\\Azure_Blob_Deploy.yml\n\t\t..\\.github\\workflows\\Choco.yml = ..\\.github\\workflows\\Choco.yml\n\t\t..\\.github\\workflows\\Deploy_Desktop.yml = ..\\.github\\workflows\\Deploy_Desktop.yml\n\t\t..\\.github\\workflows\\Deploy_Web.yml = ..\\.github\\workflows\\Deploy_Web.yml\n\t\t..\\.github\\workflows\\Sign.yml = ..\\.github\\workflows\\Sign.yml\n\t\t..\\.github\\workflows\\WinGet.yml = ..\\.github\\workflows\\WinGet.yml\n\tEndProjectSection\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tDebug|ARM64 = Debug|ARM64\n\t\tDebug|x64 = Debug|x64\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tRelease|ARM64 = Release|ARM64\n\t\tRelease|x64 = Release|x64\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Debug|ARM64.ActiveCfg = Debug|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Debug|ARM64.Build.0 = Debug|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Release|ARM64.ActiveCfg = Release|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Release|ARM64.Build.0 = Release|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{4883809C-FF4B-4504-A85E-2503607E5B99}.Release|x64.Build.0 = Release|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Debug|ARM64.ActiveCfg = Debug|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Debug|ARM64.Build.0 = Debug|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Release|ARM64.ActiveCfg = Release|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Release|ARM64.Build.0 = Release|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{E0C9DB61-A1E6-4254-A200-A946F4353D66}.Release|x64.Build.0 = Release|Any CPU\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|Any CPU.ActiveCfg = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|Any CPU.Build.0 = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|Any CPU.Deploy.0 = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|ARM64.ActiveCfg = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|ARM64.Build.0 = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|ARM64.Deploy.0 = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|x64.ActiveCfg = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|x64.Build.0 = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Debug|x64.Deploy.0 = Debug|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|Any CPU.ActiveCfg = Release|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|Any CPU.Build.0 = Release|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|Any CPU.Deploy.0 = Release|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|ARM64.ActiveCfg = Release|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|ARM64.Build.0 = Release|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|ARM64.Deploy.0 = Release|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|x64.ActiveCfg = Release|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|x64.Build.0 = Release|x64\n\t\t{12DDAD24-CCBD-409F-9342-17A0E445604F}.Release|x64.Deploy.0 = Release|x64\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Debug|ARM64.ActiveCfg = Debug|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Debug|ARM64.Build.0 = Debug|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Release|ARM64.ActiveCfg = Release|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Release|ARM64.Build.0 = Release|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{0D9ADF1D-606A-4FD1-8EA1-7619EAE16703}.Release|x64.Build.0 = Release|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Debug|ARM64.ActiveCfg = Debug|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Debug|ARM64.Build.0 = Debug|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Release|ARM64.ActiveCfg = Release|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Release|ARM64.Build.0 = Release|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{F091F5B7-84B4-48B7-A2DB-862A351F4D0A}.Release|x64.Build.0 = Release|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Debug|ARM64.ActiveCfg = Debug|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Debug|ARM64.Build.0 = Debug|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Release|ARM64.ActiveCfg = Release|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Release|ARM64.Build.0 = Release|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{D6871E74-A6E2-41EE-AA4A-7BE357501D63}.Release|x64.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{AEA7D3CB-0200-4256-BB51-D9A63E788C97} = {9BCEC868-73D3-4393-8099-D4DF2DC6DB74}\n\t\t{E1FC1E76-1B1A-411B-B5A1-42446709144B} = {9BCEC868-73D3-4393-8099-D4DF2DC6DB74}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {8B89FF65-2B56-495E-8EA6-2B279DA440AE}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "version.json",
    "content": "{\n    \"version\": \"6.0\",\n    \"publicReleaseRefSpec\": [\n        \"^refs/heads/main$\",\n        \"^refs/heads/develop$\",\n        \"^refs/heads/rel/v\\\\d+\\\\.\\\\d+\"\n    ]\n}\n"
  }
]