├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── ---support-request.md │ ├── --bug.md │ ├── --feature-request.md │ └── --thank-you.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── lock.yml ├── stale.yml ├── support.yml └── workflows │ ├── build-and-test.yml │ └── dependabot-auto-merge.yml ├── .gitignore ├── AUTHORS ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── GitReleaseManager.yaml ├── GitVersion.yml ├── LICENSE ├── README.md ├── build.cake ├── build.ps1 ├── cake.config ├── deployment └── cake │ ├── apps-uwp-tasks.cake │ ├── apps-uwp-variables.cake │ ├── apps-wpf-tasks.cake │ ├── apps-wpf-variables.cake │ ├── buildserver-continuaci.cake │ ├── buildserver.cake │ ├── codesigning-tasks.cake │ ├── codesigning-variables.cake │ ├── components-tasks.cake │ ├── components-variables.cake │ ├── dependencies-tasks.cake │ ├── dependencies-variables.cake │ ├── docker-tasks.cake │ ├── docker-variables.cake │ ├── generic-tasks.cake │ ├── generic-variables.cake │ ├── github-pages-tasks.cake │ ├── github-pages-variables.cake │ ├── installers-innosetup.cake │ ├── installers-msix.cake │ ├── installers-squirrel.cake │ ├── installers-velopack.cake │ ├── installers.cake │ ├── issuetrackers-github.cake │ ├── issuetrackers-jira.cake │ ├── issuetrackers.cake │ ├── lib-generic.cake │ ├── lib-logging.cake │ ├── lib-msbuild.cake │ ├── lib-nuget.cake │ ├── lib-signing.cake │ ├── lib-sourcelink.cake │ ├── notifications-msteams.cake │ ├── notifications.cake │ ├── sourcecontrol-github.cake │ ├── sourcecontrol.cake │ ├── tasks.cake │ ├── templates-tasks.cake │ ├── templates-variables.cake │ ├── tests-nunit.cake │ ├── tests-variables.cake │ ├── tests.cake │ ├── tools-tasks.cake │ ├── tools-variables.cake │ ├── vsextensions-tasks.cake │ └── vsextensions-variables.cake ├── design └── Package │ └── Icon.png ├── src ├── .vsconfig ├── Directory.Build.analyzers.props ├── Directory.Build.implicitusings.props ├── Directory.Build.nullable.props ├── Directory.Build.project.props ├── Directory.Build.props ├── Directory.Build.shared.explicit.props ├── Directory.Build.shared.implicit.props ├── Directory.Build.shared.mat.props ├── Directory.Build.shared.tests.props ├── Directory.Build.shared.tools.props ├── Directory.Build.shared.xamltools.props ├── Directory.Build.targets ├── GlobalSuppressions.cs ├── MethodTimeLogger.cs ├── Orc.FileSystem.Example │ ├── App.config │ ├── App.xaml │ ├── App.xaml.cs │ ├── FodyWeavers.xml │ ├── ModuleInitializer.cs │ ├── Orc.FileSystem.Example.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ViewModels │ │ └── MainViewModel.cs │ └── Views │ │ ├── MainWindow.xaml │ │ └── MainWindow.xaml.cs ├── Orc.FileSystem.Tests │ ├── FodyWeavers.xml │ ├── Helpers │ │ └── IOSynchronizationWithoutSeparateSyncFileService.cs │ ├── ModuleInitializer.cs │ ├── Orc.FileSystem.Tests.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── PublicApiFacts.Orc_FileSystem_HasNoBreakingChanges_Async.verified.txt │ ├── PublicApiFacts.cs │ ├── Services │ │ └── IOSynchronizationServiceFacts.cs │ └── TemporaryFilesContext.cs ├── Orc.FileSystem.sln ├── Orc.FileSystem │ ├── Constants.cs │ ├── Exceptions │ │ ├── FileLockScopeException.cs │ │ └── IOSynchronizationException.cs │ ├── Extensions │ │ ├── ExceptionExtensions.cs │ │ ├── FileInfoExtensions.cs │ │ ├── ILogExtensions.cs │ │ └── StreamExtensions.cs │ ├── FodyWeavers.xml │ ├── Locking │ │ ├── FileLockInfo.cs │ │ ├── FileLockScope.cs │ │ └── FileLocker.cs │ ├── ModuleInitializer.cs │ ├── MultilingualResources │ │ ├── Orc.FileSystem.de.xlf │ │ ├── Orc.FileSystem.es.xlf │ │ ├── Orc.FileSystem.fr.xlf │ │ ├── Orc.FileSystem.nl.xlf │ │ └── Orc.FileSystem.ru.xlf │ ├── Orc.FileSystem.csproj │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.de.resx │ │ ├── Resources.es.resx │ │ ├── Resources.fr.resx │ │ ├── Resources.nl.resx │ │ ├── Resources.resx │ │ └── Resources.ru.resx │ └── Services │ │ ├── DirectoryService.cs │ │ ├── EventArgs │ │ └── PathEventArgs.cs │ │ ├── Extensions │ │ ├── IDirectoryServiceExtensions.cs │ │ ├── IFileServiceExtensions.bytes.cs │ │ ├── IFileServiceExtensions.cs │ │ ├── IFileServiceExtensions.lines.cs │ │ └── IFileServiceExtensions.text.cs │ │ ├── FileService.cs │ │ ├── IOSynchronizationService.cs │ │ └── Interfaces │ │ ├── IDirectoryService.cs │ │ ├── IFileService.cs │ │ └── IIOSynchronizationService.cs ├── SolutionAssemblyInfo.cs ├── global.json └── nuget.config └── tools └── nuget.exe /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "cake.tool": { 6 | "version": "5.0.0", 7 | "commands": [ 8 | "dotnet-cake" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks you for your interest in contributing - Please see our our [Code of Conduct](CODE_OF_CONDUCT.md). 4 | 5 | 6 | ### Bug Fixes 7 | 8 | If you're looking for something to fix, please browse the open issues. 9 | 10 | Follow the style used by the [.NET Foundation](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md), with a few exceptions: 11 | 12 | - Apply readonly on class level private variables that are assigned in the constructor 13 | - 4 SPACES - tabs do not exist :) 14 | 15 | Read and follow our [Pull Request template](PULL_REQUEST_TEMPLATE.md) if you are submitting fixes. 16 | 17 | ### Feature Requests 18 | 19 | To propose a change or new feature, please make use the feature request area in issues. 20 | 21 | #### Non-Starter Topics 22 | The following topics should generally not be proposed for discussion as they are non-starters: 23 | 24 | * Large renames of APIs 25 | * Large non-backward-compatible breaking changes 26 | * Avoid clutter posts like "+1" which do not serve to further the conversation 27 | 28 | #### Proposal States 29 | ##### Open 30 | Open proposals are still under discussion. Please leave your concrete, constructive feedback on this proposal. +1s and other clutter posts which do not add to the discussion will be removed. 31 | 32 | ##### Accepted 33 | Accepted proposals are proposals that both the community and core team agree should be a part of projects. These proposals are ready for implementation. These proposals are available for anyone to work on unless it is already assigned to someone. 34 | 35 | If you wish to start working on an accepted proposal, please reply to the thread so we can mark you as the implementor and change the title to In Progress. This helps to avoid multiple people working on the same thing. If you decide to work on this proposal publicly, feel free to post a link to the branch as well for folks to follow along. 36 | 37 | ###### What "Accepted" does mean 38 | * Any community member is welcome to work on the idea. 39 | * The maintainers _may_ consider working on this idea on their own, but has not done so until it is marked "In Progress" with a team member assigned as the implementor. 40 | * Any pull request implementing the proposal will be welcomed with an API and code review. 41 | 42 | ###### What "Accepted" does not mean 43 | * The proposal will ever be implemented, either by a community member or maintainers. 44 | * The maintainers are committing to implementing a proposal, even if nobody else does. 45 | 46 | ##### In Progress 47 | Once a developer has begun work on a proposal, either from the team or a community member, the proposal is marked as in progress with the implementors name and (possibly) a link to a development branch to follow along with progress. 48 | 49 | #### Rejected 50 | Rejected proposals will not be implemented or merged into the code base. Once a proposal is rejected, the thread will be closed and the conversation is considered completed, pending considerable new information or changes.. -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: WildGums-oss # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---support-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F62C Support Request" 3 | about: Having Trouble - ONLY contributors to other OSS projects OR people who are 4 | funding this project can submit these! If you aren't one of these, expect the ban 5 | hammer to fall 6 | title: '' 7 | labels: '' 8 | assignees: '' 9 | 10 | --- 11 | 12 | ONLY active OSS contributors OR people who buy us a coffee can ask questions here. If you don't do either of these things - DO NOT FILE HERE :) 13 | 14 | Give as much details as humanly possible if you want any sort of answer! 15 | 16 | Enter Question Below (don't delete this line) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F99FBug" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: "s/unverified, t/bug \U0001F47E" 6 | assignees: '' 7 | 8 | --- 9 | 10 | # IF YOU DON'T ANSWER THIS TEMPLATE - THE BOT WILL AUTOMATICALLY CLOSE YOUR ISSUE! 11 | 12 | ## Please check all of the platforms you are having the issue on (if platform is not listed, it is not supported) 13 | 14 | - [ ] WPF 15 | - [ ] Blazor WASM 16 | - [ ] .NET Core 17 | 18 | ## Component 19 | 20 | What component is this issue occurring in? 21 | 22 | ## Version of Library 23 | 24 | 25 | ## Version of OS(s) listed above with issue 26 | 27 | 28 | ## Steps to Reproduce 29 | 1. 30 | 2. 31 | 3. 32 | 33 | ## Expected Behavior 34 | 35 | 36 | ## Actual Behavior -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F354Feature Request" 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: "s/unverified, t/enhancement \U0001F423" 6 | assignees: '' 7 | 8 | --- 9 | 10 | # IF YOU DON'T ANSWER THIS TEMPLATE - THE BOT WILL AUTOMATICALLY CLOSE YOUR ISSUE! 11 | 12 | ## Summary 13 | Please provide a brief summary of your proposal. Two to three sentences is best here. 14 | 15 | ## API Changes 16 | Include a list of all API changes, additions, subtractions as would be required by your proposal. These APIs should be considered placeholders, so the naming is not as important as getting the concepts correct. If possible you should include some "example" code of usage of your new API. 17 | 18 | ## Intended Use Case 19 | Provide a detailed example of where your proposal would be used and for what purpose. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--thank-you.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "❤️Thank You" 3 | about: Just want to say thank you, this is the one to do it in 4 | title: Thank You 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Leave Your Message Below (don't delete this line though) -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description of Change ### 2 | 3 | 4 | 5 | ### Issues Resolved ### 6 | 7 | 8 | 9 | - fixes # 10 | 11 | ### API Changes ### 12 | 13 | 14 | 15 | None 16 | 17 | ### Behavioral Changes ### 18 | 19 | 20 | 21 | None 22 | 23 | ### Testing Procedure ### 24 | 25 | 26 | 27 | ### PR Checklist ### 28 | 29 | - [ ] I have included examples or tests 30 | - [ ] I have updated the change log or created a GitHub ticket with the change 31 | - [ ] I am listed in the CONTRIBUTORS file (if it exists) 32 | - [ ] Changes adhere to coding standard 33 | - [ ] I checked the licenses of Third Party software and discussed new dependencies with at least 1 other team member -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | registries: 3 | nuget-feed-default: 4 | type: nuget-feed 5 | url: https://api.nuget.org/v3/index.json 6 | 7 | updates: 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: weekly 12 | 13 | - package-ecosystem: nuget 14 | directories: 15 | - "/src" 16 | schedule: 17 | interval: daily 18 | open-pull-requests-limit: 10 19 | ignore: 20 | - dependency-name: "*Analyzers" 21 | versions: 22 | - ">= 0" 23 | registries: 24 | - nuget-feed-default -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Lock Threads - https://github.com/dessant/lock-threads 2 | 3 | # Number of days of inactivity before a closed issue or pull request is locked 4 | daysUntilLock: 4 5 | 6 | # Skip issues and pull requests created before a given timestamp. Timestamp must 7 | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable 8 | skipCreatedBefore: false 9 | 10 | # Issues and pull requests with these labels will be ignored. Set to `[]` to disable 11 | exemptLabels: ["pinned", "planned"] 12 | 13 | # Label to add before locking, such as `outdated`. Set to `false` to disable 14 | lockLabel: false 15 | 16 | # Comment to post before locking. Set to `false` to disable 17 | lockComment: > 18 | This thread has been automatically locked since there has not been 19 | any recent activity after it was closed. Please open a new issue for 20 | related bugs. 21 | 22 | # Assign `resolved` as the reason for locking. Set to `false` to disable 23 | setLockReason: true 24 | 25 | # Limit to only `issues` or `pulls` 26 | # only: issues 27 | 28 | # Optionally, specify configuration settings just for `issues` or `pulls` 29 | # issues: 30 | # exemptLabels: 31 | # - help-wanted 32 | # lockLabel: outdated 33 | 34 | # pulls: 35 | # daysUntilLock: 30 36 | 37 | # Repository to extend settings from 38 | # _extends: repo -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - planned 10 | - nostale 11 | # Label to use when marking an issue as stale 12 | staleLabel: wontfix 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. Thank you 17 | for your contributions. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: false -------------------------------------------------------------------------------- /.github/support.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Support Requests - https://github.com/dessant/support-requests 2 | 3 | # Label used to mark issues as support requests 4 | supportLabel: support 5 | 6 | # Comment to post on issues marked as support requests, `{issue-author}` is an 7 | # optional placeholder. Set to `false` to disable 8 | supportComment: > 9 | :wave: @{issue-author}, we use the issue tracker exclusively for bug reports 10 | and feature requests. However, this issue appears to be a support request. 11 | Please use our support channels to get help with the project. 12 | 13 | # Close issues marked as support requests 14 | close: true 15 | 16 | # Lock issues marked as support requests 17 | lock: false 18 | 19 | # Assign `off-topic` as the reason for locking. Set to `false` to disable 20 | setLockReason: true 21 | 22 | # Repository to extend settings from 23 | # _extends: repo -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - master 8 | pull_request: 9 | 10 | #permissions: 11 | #pull-requests: write 12 | #contents: write 13 | 14 | jobs: 15 | build-and-test: 16 | runs-on: windows-latest # Required for some (WPF) projects 17 | 18 | steps: 19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 20 | id: checkout 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Setup .NET Core 25 | id: setup-dotnet 26 | uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 27 | with: 28 | dotnet-version: '9.0.x' 29 | 30 | - name: Cake Action 31 | id: cake-action 32 | uses: cake-build/cake-action@5167c3f6a9e15c76f009de2acdfb9488552bc0b9 #v3.0.0 33 | with: 34 | target: BuildAndTest 35 | arguments: | 36 | IsCiBuild: true -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request_target 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | jobs: 7 | dependabot: 8 | runs-on: ubuntu-latest 9 | # Checking the actor will prevent your Action run failing on non-Dependabot PRs 10 | if: ${{ github.actor == 'dependabot[bot]' }} 11 | steps: 12 | - name: Dependabot metadata 13 | id: dependabot-metadata 14 | uses: dependabot/fetch-metadata@08eff52bf64351f401fb50d4972fa95b9f2c2d1b #2.4.0 15 | with: 16 | github-token: "${{ secrets.GITHUB_TOKEN }}" 17 | - name: Approve Dependabot PR 18 | run: gh pr review --approve "$PR_URL" 19 | env: 20 | PR_URL: ${{github.event.pull_request.html_url}} 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | - name: Enable auto-merge for Dependabot PRs 23 | if: | 24 | (startsWith(steps.dependabot-metadata.outputs.dependency-names, 'catel.') || 25 | startsWith(steps.dependabot-metadata.outputs.dependency-names, 'orc.') || 26 | startsWith(steps.dependabot-metadata.outputs.dependency-names, 'orchestra.')) && 27 | (steps.dependabot-metadata.outputs.update-type == 'version-update:semver-minor' || 28 | steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch') 29 | run: gh pr merge --auto --merge "$PR_URL" 30 | env: 31 | PR_URL: ${{github.event.pull_request.html_url}} 32 | PR_NUMBER: ${{github.event.pull_request.number}} 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | .nuget/ 10 | tools/FAKE/ 11 | build-log.xml 12 | Nuget.key 13 | TestResult.xml 14 | 15 | # Build results 16 | [Bb]in/ 17 | [Cc]lientbin/ 18 | [Dd]ebug/ 19 | [Rr]elease/ 20 | [Oo]utput*/ 21 | [Pp]ackages*/ 22 | [Tt]emp/ 23 | bin 24 | obj 25 | [Ll]ib/* 26 | ![Ll]ib/repositories.config 27 | *.ide/ 28 | *_i.c 29 | *_p.c 30 | *.ilk 31 | *.meta 32 | *.obj 33 | *.orig 34 | *.pch 35 | *.pdb 36 | *.pgc 37 | *.pgd 38 | *.rsp 39 | *.sbr 40 | *.tlb 41 | *.tli 42 | *.tlh 43 | *.tmp 44 | *.vspscc 45 | *.xap 46 | .builds 47 | *.log 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | 56 | # Visual Studio profiler 57 | *.psess 58 | *.vsp 59 | 60 | # ReSharper is a .NET coding add-in 61 | _ReSharper* 62 | *.resharper.user 63 | 64 | # Catel 65 | CatelLogging.txt 66 | 67 | # Dotcover 68 | *.dotCover 69 | *.dotsettings.user 70 | 71 | # Finalbuilder 72 | *.fbl7 73 | *.fb7lck 74 | *.fbpInf 75 | 76 | # Ghostdoc 77 | *.GhostDoc.xml 78 | 79 | # Deployments 80 | deployment/FinalBuilder/backup 81 | deployment/InnoSetup/template/templates 82 | deployment/InnoSetup/template/snippets 83 | deployment/InnoSetup/template/libraries 84 | deployment/InnoSetup/template/doc 85 | 86 | # Installshield output folder 87 | [Ee]xpress 88 | 89 | # DocProject is a documentation generator add-in 90 | DocProject/buildhelp/ 91 | DocProject/Help/*.HxT 92 | DocProject/Help/*.HxC 93 | DocProject/Help/*.hhc 94 | DocProject/Help/*.hhk 95 | DocProject/Help/*.hhp 96 | DocProject/Help/Html2 97 | DocProject/Help/html 98 | 99 | # Click-Once directory 100 | publish 101 | 102 | # Others 103 | [Bb]in 104 | [Oo]bj 105 | sql 106 | TestResults 107 | *.Cache 108 | ClientBin 109 | stylecop.* 110 | ~$* 111 | *.dbmdl 112 | Generated_Code #added for RIA/Silverlight projects 113 | 114 | # Backup & report files from converting an old project file to a newer 115 | # Visual Studio version. Backup files are not needed, because we have git ;-) 116 | _UpgradeReport_Files/ 117 | Backup*/ 118 | UpgradeLog*.XML 119 | 120 | # Windows image file caches 121 | Thumbs.db 122 | 123 | # Folder config file 124 | Desktop.ini 125 | 126 | # Cake - Uncomment if you are using it 127 | tools/** 128 | !tools/packages.config 129 | build.cakeoverrides 130 | 131 | # mstest test results 132 | TestResults 133 | .vs/ 134 | .sonarqube/ 135 | BundleArtifacts/ 136 | 137 | # docker / tye 138 | .tye 139 | 140 | # editors 141 | .idea 142 | .vscode 143 | 144 | # Binaries 145 | *.dll 146 | *.exe 147 | 148 | # fody 149 | FodyWeavers.xsd 150 | 151 | # Approval tests 152 | *.received.* 153 | 154 | # ANTLR 155 | data/dsl/*.class 156 | data/dsl/*.java 157 | 158 | # Nodejs / NPM 159 | node_modules 160 | package-lock.json 161 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS file. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | # Please keep the list sorted. 10 | # Please notify the first person on the list to be added here. 11 | 12 | WildGums 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributors 2 | ============ 3 | 4 | ## Contributing Process 5 | 6 | ### Get Buyoff Or Find Open Community Issues/Features 7 | 8 | * Through [GitHub](https://github.com/WildGums/Orc.FileSystem/issues), or through the [Chat Room](https://gitter.im/WildGums/Orc.FileSystem) (preferred), you talk about a feature you would like to see (or a bug), and why it should be included. 9 | * If approved through the Chat Room, ensure the accompanying issue is created with information and a link back to the Chat Room discussion. 10 | * Once you get a nod you can start on the feature. 11 | 12 | ### Set Up Your Environment 13 | 14 | * You create, or update, a fork of https://github.com/WildGums/Orc.FileSystem under your GitHub account. 15 | * From there you create a branch named specific to the feature. 16 | * In the branch you do work specific to the feature. 17 | * Please also observe the following: 18 | * No reformatting 19 | * No changing files that are not specific to the feature 20 | * More covered below in the **Prepare commits** section. 21 | * Test your changes and please help us out by updating and implementing some automated tests. It is recommended that all contributors spend some time looking over the tests in the source code. You can't go wrong emulating one of the existing tests and then changing it specific to the behavior you are testing. 22 | * Please do not update your branch from the master unless we ask you to. See the responding to feedback section below. 23 | 24 | ### Prepare Commits 25 | 26 | This section serves to help you understand what makes a good commit. 27 | 28 | A commit should observe the following: 29 | 30 | * A commit is a small logical unit that represents a change. 31 | * Should include new or changed tests relevant to the changes you are making. 32 | * No unnecessary whitespace. Check for whitespace with `git diff --check` and `git diff --cached --check` before commit. 33 | * You can stage parts of a file for commit. 34 | 35 | A commit message should observe the following: 36 | 37 | * The first line of the commit message should be a short description around 50 characters in length and be prefixed with the issue it refers to with parentheses surrounding that. If the issue is #25, you should have `#25` prefixed to the message. 38 | * If the commit is about documentation, the message should be prefixed with `(doc)`. 39 | * If it is a trivial commit or one of formatting/spaces fixes, it should be prefixed with `(maint)`. 40 | * After the subject, skip one line and fill out a body if the subject line is not informative enough. 41 | * The body: 42 | * Should indent at `72` characters. 43 | * Explains more fully the reason(s) for the change and contrasts with previous behavior. 44 | * Uses present tense. "Fix" versus "Fixed". 45 | 46 | A good example of a commit message is as follows: 47 | 48 | ``` 49 | #7 Improve initialization performance 50 | 51 | Previously the initialization took 400 ms and had a severe impact 52 | on the startup of an application. This commit brings the initialization 53 | time back to 35 ms. 54 | ``` 55 | 56 | ### Submit Pull Request (PR) 57 | 58 | Prerequisites: 59 | 60 | * You are making commits in a feature branch. 61 | * All specs should be passing. 62 | 63 | Submitting PR: 64 | 65 | * Once you feel it is ready, submit the pull request to the `WildGums/Orc.FileSystem` repository against the ````develop```` branch ([more information on this can be found here](https://help.github.com/articles/creating-a-pull-request)). 66 | * In the pull request, outline what you did and point to specific conversations (as in URLs) and issues that you are are resolving. This is a tremendous help for us in evaluation and acceptance. 67 | * Once the pull request is in, please do not delete the branch or close the pull request (unless something is wrong with it). 68 | * One of the Team members, or one of the committers, will evaluate it within a reasonable time period (which is to say usually within 0-2 weeks). Some things get evaluated faster or fast tracked. We are human and we have active lives outside of open source so don't fret if you haven't seen any activity on your pull request within a month or two. We don't have a Service Level Agreement (SLA) for pull requests. Just know that we will evaluate your pull request. 69 | 70 | ### Respond to Feedback on Pull Request 71 | 72 | We may have feedback for you to fix or change some things. We generally like to see that pushed against the same topic branch (it will automatically update the Pull Request). You can also fix/squash/rebase commits and push the same topic branch with `--force` (it's generally acceptable to do this on topic branches not in the main repository, it is generally unacceptable and should be avoided at all costs against the main repository). 73 | 74 | If we have comments or questions when we do evaluate it and receive no response, it will probably lessen the chance of getting accepted. Eventually this means it will be closed if it is not accepted. Please know this doesn't mean we don't value your contribution, just that things go stale. If in the future you want to pick it back up, feel free to address our concerns/questions/feedback and reopen the issue/open a new PR (referencing old one). 75 | 76 | Sometimes we may need you to rebase your commit against the latest code before we can review it further. If this happens, you can do the following: 77 | 78 | * `git fetch upstream` (upstream would be the mainstream repo or `WildGums/Orc.FileSystem` in this case) 79 | * `git checkout develop` 80 | * `git rebase upstream/develop` 81 | * `git checkout your-branch` 82 | * `git rebase develop` 83 | * Fix any merge conflicts 84 | * `git push origin your-branch` (origin would be your GitHub repo or `your-github-username/Orc.FileSystem` in this case). You may need to `git push origin your-branch --force` to get the commits pushed. This is generally acceptable with topic branches not in the mainstream repository. 85 | 86 | The only reasons a pull request should be closed and resubmitted are as follows: 87 | 88 | * When the pull request is targeting the wrong branch (this doesn't happen as often). 89 | * When there are updates made to the original by someone other than the original contributor. Then the old branch is closed with a note on the newer branch this supersedes the issue. 90 | 91 | ## Other General Information 92 | 93 | If you reformat code or hit core functionality without an approval from a person on the Team, it's likely that no matter how awesome it looks afterwards, it will probably not get accepted. Reformatting code makes it harder for us to evaluate exactly what was changed. 94 | 95 | If you do these things, it will be make evaluation and acceptance easy. Now if you stray outside of the guidelines we have above, it doesn't mean we are going to ignore your pull request. It will just make things harder for us. Harder for us roughly translates to a longer SLA for your pull request. -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who have contributed 2 | # to this repository. 3 | # The AUTHORS file lists the copyright holders; this file 4 | # lists people. 5 | 6 | # People submitting code should be listed in this file (by email address). 7 | 8 | # Names should be added to this file like so: 9 | # Name 10 | 11 | # Please keep the list sorted. 12 | 13 | Geert van Horrik 14 | Maksim Khomutov 15 | -------------------------------------------------------------------------------- /GitReleaseManager.yaml: -------------------------------------------------------------------------------- 1 | issue-labels-include: 2 | - Breaking change 3 | - Feature 4 | - Bug 5 | - Improvement 6 | - Documentation 7 | - Dependencies 8 | issue-labels-exclude: 9 | - Build 10 | - Won't fix 11 | issue-labels-alias: 12 | - name: Documentation 13 | header: Documentation 14 | plural: Documentation -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDeployment 2 | assembly-versioning-scheme: MajorMinorPatch 3 | next-version: 5.0.0 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 WildGums 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ============================================================================== 24 | 25 | Additional licenses / components 26 | -------------------------------- 27 | 28 | Some parts are based on other open-source components. Each component that is based 29 | on another component has a reference to the original component including the original 30 | license the code was delivered upon. Below is a list of other components / licenses. 31 | 32 | Note: if you feel an item is missing in this list, please let us know! 33 | 34 | * Catel 35 | https://github.com/catel/catel 36 | Original license: MIT -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Orc.FileSystem 2 | ============== 3 | 4 | Name|Badge 5 | ---|--- 6 | Chat|[![Join the chat at https://gitter.im/WildGums/Orc.FileSystem](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/WildGums/Orc.FileSystem?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | Downloads|![NuGet downloads](https://img.shields.io/nuget/dt/orc.filesystem.svg) 8 | Stable version|![Version](https://img.shields.io/nuget/v/orc.filesystem.svg) 9 | Unstable version|![Pre-release version](https://img.shields.io/nuget/vpre/orc.filesystem.svg) 10 | 11 | This library wraps file system methods inside services. The advantages are: 12 | 13 | - All operations are being logged and can easily be accessed (even in production scenarios) 14 | - All operations are wrapped inside try/catch so all failures are logged as well 15 | - Services allow easier mocking for unit tests 16 | 17 | For documentation, please visit the [documentation portal](http://opensource.wildgums.com) -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | //======================================================= 2 | // DEFINE PARAMETERS 3 | //======================================================= 4 | 5 | // Define the required parameters 6 | var Parameters = new Dictionary(); 7 | Parameters["SolutionName"] = "Orc.FileSystem"; 8 | Parameters["Company"] = "WildGums"; 9 | Parameters["RepositoryUrl"] = string.Format("https://github.com/{0}/{1}", GetBuildServerVariable("Company"), GetBuildServerVariable("SolutionName")); 10 | Parameters["StartYear"] = "2014"; 11 | Parameters["UseVisualStudioPrerelease"] = "false"; 12 | 13 | // Note: the rest of the variables should be coming from the build server, 14 | // see `/deployment/cake/*-variables.cake` for customization options 15 | // 16 | // If required, more variables can be overridden by specifying them via the 17 | // Parameters dictionary, but the build server variables will always override 18 | // them if defined by the build server. For example, to override the code 19 | // sign wild card, add this to build.cake 20 | // 21 | // Parameters["CodeSignWildcard"] = "Orc.EntityFramework"; 22 | 23 | //======================================================= 24 | // DEFINE COMPONENTS TO BUILD / PACKAGE 25 | //======================================================= 26 | 27 | Components.Add(string.Format("{0}", GetBuildServerVariable("SolutionName"))); 28 | 29 | TestProjects.Add(string.Format("{0}.Tests", GetBuildServerVariable("SolutionName"))); 30 | 31 | //======================================================= 32 | // REQUIRED INITIALIZATION, DO NOT CHANGE 33 | //======================================================= 34 | 35 | // Now all variables are defined, include the tasks, that 36 | // script will take care of the rest of the magic 37 | 38 | #l "./deployment/cake/tasks.cake" 39 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | 3 | Set-Location -LiteralPath $PSScriptRoot 4 | 5 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' 6 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = '1' 7 | $env:DOTNET_NOLOGO = '1' 8 | 9 | dotnet tool restore 10 | if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } 11 | 12 | dotnet cake @args 13 | if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } 14 | -------------------------------------------------------------------------------- /cake.config: -------------------------------------------------------------------------------- 1 | ; The configuration file for Cake. 2 | 3 | [Settings] 4 | SkipVerification=true 5 | 6 | [Settings] 7 | EnableScriptCache=true 8 | 9 | [Paths] 10 | ; Cache=%temp%/cake-build/cache/ 11 | ; Note: cache-path is set via environment variables -------------------------------------------------------------------------------- /deployment/cake/apps-uwp-variables.cake: -------------------------------------------------------------------------------- 1 | #l "./buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class UwpContext : BuildContextWithItemsBase 6 | { 7 | public UwpContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | public string WindowsStoreAppId { get; set; } 13 | public string WindowsStoreClientId { get; set; } 14 | public string WindowsStoreClientSecret { get; set; } 15 | public string WindowsStoreTenantId { get; set; } 16 | 17 | protected override void ValidateContext() 18 | { 19 | } 20 | 21 | protected override void LogStateInfoForContext() 22 | { 23 | CakeContext.Information($"Found '{Items.Count}' uwp projects"); 24 | } 25 | } 26 | 27 | //------------------------------------------------------------- 28 | 29 | private UwpContext InitializeUwpContext(BuildContext buildContext, IBuildContext parentBuildContext) 30 | { 31 | var data = new UwpContext(parentBuildContext) 32 | { 33 | Items = UwpApps ?? new List(), 34 | WindowsStoreAppId = buildContext.BuildServer.GetVariable("WindowsStoreAppId", showValue: true), 35 | WindowsStoreClientId = buildContext.BuildServer.GetVariable("WindowsStoreClientId", showValue: false), 36 | WindowsStoreClientSecret = buildContext.BuildServer.GetVariable("WindowsStoreClientSecret", showValue: false), 37 | WindowsStoreTenantId = buildContext.BuildServer.GetVariable("WindowsStoreTenantId", showValue: false) 38 | }; 39 | 40 | return data; 41 | } 42 | 43 | //------------------------------------------------------------- 44 | 45 | List _uwpApps; 46 | 47 | public List UwpApps 48 | { 49 | get 50 | { 51 | if (_uwpApps is null) 52 | { 53 | _uwpApps = new List(); 54 | } 55 | 56 | return _uwpApps; 57 | } 58 | } -------------------------------------------------------------------------------- /deployment/cake/apps-wpf-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class WpfContext : BuildContextWithItemsBase 6 | { 7 | public WpfContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | 13 | public string DeploymentsShare { get; set; } 14 | public string Channel { get; set; } 15 | public bool AppendDeploymentChannelSuffix { get; set; } 16 | public bool UpdateDeploymentsShare { get; set; } 17 | public string AzureDeploymentsStorageConnectionString { get; set; } 18 | 19 | public bool GenerateDeploymentCatalog { get; set; } 20 | public bool GroupUpdatesByMajorVersion { get; set; } 21 | public bool DeployUpdatesToAlphaChannel { get; set; } 22 | public bool DeployUpdatesToBetaChannel { get; set; } 23 | public bool DeployUpdatesToStableChannel { get; set; } 24 | public bool DeployInstallers { get; set; } 25 | 26 | protected override void ValidateContext() 27 | { 28 | 29 | } 30 | 31 | protected override void LogStateInfoForContext() 32 | { 33 | CakeContext.Information($"Found '{Items.Count}' wpf projects"); 34 | 35 | CakeContext.Information($"Generate Deployment Catalog: '{GenerateDeploymentCatalog}'"); 36 | CakeContext.Information($"Group updates by major version: '{GroupUpdatesByMajorVersion}'"); 37 | CakeContext.Information($"Deploy updates to alpha channel: '{DeployUpdatesToAlphaChannel}'"); 38 | CakeContext.Information($"Deploy updates to beta channel: '{DeployUpdatesToBetaChannel}'"); 39 | CakeContext.Information($"Deploy updates to stable channel: '{DeployUpdatesToStableChannel}'"); 40 | CakeContext.Information($"Deploy installers: '{DeployInstallers}'"); 41 | } 42 | 43 | public string GetDeploymentShareForProject(string projectName) 44 | { 45 | var projectSlug = GetProjectSlug(projectName, "-"); 46 | var deploymentShare = System.IO.Path.Combine(DeploymentsShare, projectSlug); 47 | 48 | return deploymentShare; 49 | } 50 | } 51 | 52 | //------------------------------------------------------------- 53 | 54 | private WpfContext InitializeWpfContext(BuildContext buildContext, IBuildContext parentBuildContext) 55 | { 56 | var data = new WpfContext(parentBuildContext) 57 | { 58 | Items = WpfApps ?? new List(), 59 | DeploymentsShare = buildContext.BuildServer.GetVariable("DeploymentsShare", showValue: true), 60 | Channel = buildContext.BuildServer.GetVariable("Channel", showValue: true), 61 | AppendDeploymentChannelSuffix = buildContext.BuildServer.GetVariableAsBool("AppendDeploymentChannelSuffix", false, showValue: true), 62 | UpdateDeploymentsShare = buildContext.BuildServer.GetVariableAsBool("UpdateDeploymentsShare", true, showValue: true), 63 | AzureDeploymentsStorageConnectionString = buildContext.BuildServer.GetVariable("AzureDeploymentsStorageConnectionString"), 64 | GenerateDeploymentCatalog = buildContext.BuildServer.GetVariableAsBool("WpfGenerateDeploymentCatalog", true, showValue: true), 65 | GroupUpdatesByMajorVersion = buildContext.BuildServer.GetVariableAsBool("WpfGroupUpdatesByMajorVersion", false, showValue: true), 66 | DeployUpdatesToAlphaChannel = buildContext.BuildServer.GetVariableAsBool("WpfDeployUpdatesToAlphaChannel", true, showValue: true), 67 | DeployUpdatesToBetaChannel = buildContext.BuildServer.GetVariableAsBool("WpfDeployUpdatesToBetaChannel", true, showValue: true), 68 | DeployUpdatesToStableChannel = buildContext.BuildServer.GetVariableAsBool("WpfDeployUpdatesToStableChannel", true, showValue: true), 69 | DeployInstallers = buildContext.BuildServer.GetVariableAsBool("WpfDeployInstallers", true, showValue: true), 70 | }; 71 | 72 | if (string.IsNullOrWhiteSpace(data.Channel)) 73 | { 74 | data.Channel = DetermineChannel(buildContext.General); 75 | 76 | data.CakeContext.Information($"Determined channel '{data.Channel}' for wpf projects"); 77 | } 78 | 79 | return data; 80 | } 81 | 82 | //------------------------------------------------------------- 83 | 84 | List _wpfApps; 85 | 86 | public List WpfApps 87 | { 88 | get 89 | { 90 | if (_wpfApps is null) 91 | { 92 | _wpfApps = new List(); 93 | } 94 | 95 | return _wpfApps; 96 | } 97 | } -------------------------------------------------------------------------------- /deployment/cake/buildserver-continuaci.cake: -------------------------------------------------------------------------------- 1 | public class ContinuaCIBuildServer : BuildServerBase 2 | { 3 | public ContinuaCIBuildServer(ICakeContext cakeContext) 4 | : base(cakeContext) 5 | { 6 | } 7 | 8 | //------------------------------------------------------------- 9 | 10 | public override async Task OnTestFailedAsync() 11 | { 12 | await ImportUnitTestsAsync(); 13 | } 14 | 15 | //------------------------------------------------------------- 16 | 17 | public override async Task AfterTestAsync() 18 | { 19 | await ImportUnitTestsAsync(); 20 | } 21 | 22 | //------------------------------------------------------------- 23 | 24 | private async Task ImportUnitTestsAsync() 25 | { 26 | foreach (var project in BuildContext.Tests.Items) 27 | { 28 | await ImportTestFilesAsync(project); 29 | } 30 | } 31 | 32 | //------------------------------------------------------------- 33 | 34 | private async Task ImportTestFilesAsync(string projectName) 35 | { 36 | var continuaCIContext = GetContinuaCIContext(); 37 | if (!continuaCIContext.IsRunningOnContinuaCI) 38 | { 39 | return; 40 | } 41 | 42 | CakeContext.Warning($"Importing test results for '{projectName}'"); 43 | 44 | var testResultsDirectory = System.IO.Path.Combine(BuildContext.General.OutputRootDirectory, "testresults"); 45 | 46 | if (!CakeContext.DirectoryExists(testResultsDirectory)) 47 | { 48 | CakeContext.Warning("No test results directory"); 49 | return; 50 | } 51 | 52 | var type = string.Empty; 53 | var importType = string.Empty; 54 | 55 | if (IsNUnitTestProject(BuildContext, projectName)) 56 | { 57 | type = "nunit"; 58 | importType = "nunit"; 59 | } 60 | 61 | if (IsXUnitTestProject(BuildContext, projectName)) 62 | { 63 | type = "xunit"; 64 | importType = "mstest"; // Xml type is different 65 | } 66 | 67 | if (string.IsNullOrWhiteSpace(type)) 68 | { 69 | CakeContext.Warning("Could not find test project type"); 70 | return; 71 | } 72 | 73 | CakeContext.Warning($"Determined project type '{type}'"); 74 | 75 | var cakeFilePattern = System.IO.Path.Combine(testResultsDirectory, projectName, "*.xml"); 76 | 77 | CakeContext.Warning($"Using pattern '{cakeFilePattern}'"); 78 | 79 | var testResultsFiles = CakeContext.GetFiles(cakeFilePattern); 80 | if (!testResultsFiles.Any()) 81 | { 82 | CakeContext.Warning($"No test result file found using '{cakeFilePattern}'"); 83 | return; 84 | } 85 | 86 | var continuaCiFilePattern = System.IO.Path.Combine(testResultsDirectory, "**.xml"); 87 | 88 | CakeContext.Information($"Importing test results from using '{continuaCiFilePattern}' using import type '{importType}'"); 89 | 90 | var message = $"@@continua[importUnitTestResults type='{importType}' filePatterns='{cakeFilePattern}']"; 91 | WriteIntegration(message); 92 | } 93 | 94 | //------------------------------------------------------------- 95 | 96 | public override async Task PinBuildAsync(string comment) 97 | { 98 | var continuaCIContext = GetContinuaCIContext(); 99 | if (!continuaCIContext.IsRunningOnContinuaCI) 100 | { 101 | return; 102 | } 103 | 104 | CakeContext.Information("Pinning build in Continua CI"); 105 | 106 | var message = string.Format("@@continua[pinBuild comment='{0}' appendComment='{1}']", 107 | comment, !string.IsNullOrWhiteSpace(comment)); 108 | WriteIntegration(message); 109 | } 110 | 111 | //------------------------------------------------------------- 112 | 113 | public override async Task SetVersionAsync(string version) 114 | { 115 | var continuaCIContext = GetContinuaCIContext(); 116 | if (!continuaCIContext.IsRunningOnContinuaCI) 117 | { 118 | return; 119 | } 120 | 121 | CakeContext.Information("Setting version '{0}' in Continua CI", version); 122 | 123 | var message = string.Format("@@continua[setBuildVersion value='{0}']", version); 124 | WriteIntegration(message); 125 | } 126 | 127 | //------------------------------------------------------------- 128 | 129 | public override async Task SetVariableAsync(string variableName, string value) 130 | { 131 | var continuaCIContext = GetContinuaCIContext(); 132 | if (!continuaCIContext.IsRunningOnContinuaCI) 133 | { 134 | return; 135 | } 136 | 137 | CakeContext.Information("Setting variable '{0}' to '{1}' in Continua CI", variableName, value); 138 | 139 | var message = string.Format("@@continua[setVariable name='{0}' value='{1}' skipIfNotDefined='true']", variableName, value); 140 | WriteIntegration(message); 141 | } 142 | 143 | //------------------------------------------------------------- 144 | 145 | public override Tuple GetVariable(string variableName, string defaultValue) 146 | { 147 | var continuaCIContext = GetContinuaCIContext(); 148 | if (!continuaCIContext.IsRunningOnContinuaCI) 149 | { 150 | return new Tuple(false, string.Empty); 151 | } 152 | 153 | var exists = false; 154 | var value = string.Empty; 155 | 156 | var buildServerVariables = continuaCIContext.Environment.Variable; 157 | if (buildServerVariables.ContainsKey(variableName)) 158 | { 159 | CakeContext.Information("Variable '{0}' is specified via Continua CI", variableName); 160 | 161 | exists = true; 162 | value = buildServerVariables[variableName]; 163 | } 164 | 165 | return new Tuple(exists, value); 166 | } 167 | 168 | //------------------------------------------------------------- 169 | 170 | private IContinuaCIProvider GetContinuaCIContext() 171 | { 172 | return CakeContext.ContinuaCI(); 173 | } 174 | 175 | //------------------------------------------------------------- 176 | 177 | private void WriteIntegration(string message) 178 | { 179 | // Must be Console.WriteLine 180 | CakeContext.Information(message); 181 | } 182 | } -------------------------------------------------------------------------------- /deployment/cake/codesigning-tasks.cake: -------------------------------------------------------------------------------- 1 | #l "codesigning-variables.cake" 2 | 3 | using System.Xml.Linq; 4 | 5 | //------------------------------------------------------------- 6 | 7 | // Empty by design for now 8 | -------------------------------------------------------------------------------- /deployment/cake/codesigning-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class CodeSigningContext : BuildContextBase 6 | { 7 | public CodeSigningContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | public List ProjectsToSignImmediately { get; set; } 13 | 14 | protected override void ValidateContext() 15 | { 16 | 17 | } 18 | 19 | protected override void LogStateInfoForContext() 20 | { 21 | //CakeContext.Information($"Found '{Items.Count}' component projects"); 22 | } 23 | } 24 | 25 | //------------------------------------------------------------- 26 | 27 | private CodeSigningContext InitializeCodeSigningContext(BuildContext buildContext, IBuildContext parentBuildContext) 28 | { 29 | var data = new CodeSigningContext(parentBuildContext) 30 | { 31 | ProjectsToSignImmediately = CodeSignImmediately, 32 | }; 33 | 34 | return data; 35 | } 36 | 37 | //------------------------------------------------------------- 38 | 39 | List _codeSignImmediately; 40 | 41 | public List CodeSignImmediately 42 | { 43 | get 44 | { 45 | if (_codeSignImmediately is null) 46 | { 47 | _codeSignImmediately = new List(); 48 | } 49 | 50 | return _codeSignImmediately; 51 | } 52 | } -------------------------------------------------------------------------------- /deployment/cake/components-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class ComponentsContext : BuildContextWithItemsBase 6 | { 7 | public ComponentsContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | public string NuGetRepositoryUrl { get; set; } 13 | public string NuGetRepositoryApiKey { get; set; } 14 | 15 | protected override void ValidateContext() 16 | { 17 | 18 | } 19 | 20 | protected override void LogStateInfoForContext() 21 | { 22 | CakeContext.Information($"Found '{Items.Count}' component projects"); 23 | } 24 | } 25 | 26 | //------------------------------------------------------------- 27 | 28 | private ComponentsContext InitializeComponentsContext(BuildContext buildContext, IBuildContext parentBuildContext) 29 | { 30 | var data = new ComponentsContext(parentBuildContext) 31 | { 32 | Items = Components ?? new List(), 33 | NuGetRepositoryUrl = buildContext.BuildServer.GetVariable("NuGetRepositoryUrl", showValue: true), 34 | NuGetRepositoryApiKey = buildContext.BuildServer.GetVariable("NuGetRepositoryApiKey", showValue: false) 35 | }; 36 | 37 | return data; 38 | } 39 | 40 | //------------------------------------------------------------- 41 | 42 | List _components; 43 | 44 | public List Components 45 | { 46 | get 47 | { 48 | if (_components is null) 49 | { 50 | _components = new List(); 51 | } 52 | 53 | return _components; 54 | } 55 | } -------------------------------------------------------------------------------- /deployment/cake/dependencies-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class DependenciesContext : BuildContextWithItemsBase 6 | { 7 | public DependenciesContext(IBuildContext parentBuildContext, Dictionary> dependencies) 8 | : base(parentBuildContext) 9 | { 10 | Dependencies = dependencies ?? new Dictionary>(); 11 | Items = Dependencies.Keys.ToList(); 12 | } 13 | 14 | public Dictionary> Dependencies { get; private set; } 15 | 16 | protected override void ValidateContext() 17 | { 18 | 19 | } 20 | 21 | protected override void LogStateInfoForContext() 22 | { 23 | CakeContext.Information($"Found '{Items.Count}' dependency projects"); 24 | } 25 | 26 | public bool ShouldBuildDependency(string dependencyProject) 27 | { 28 | return ShouldBuildDependency(dependencyProject, Array.Empty()); 29 | } 30 | 31 | public bool ShouldBuildDependency(string dependencyProject, IEnumerable knownDependenciesToBeBuilt) 32 | { 33 | if (!Dependencies.TryGetValue(dependencyProject, out var dependencyInfo)) 34 | { 35 | return false; 36 | } 37 | 38 | if (dependencyInfo.Count == 0) 39 | { 40 | // No explicit projects defined, always build dependency 41 | return true; 42 | } 43 | 44 | foreach (var projectRequiringDependency in dependencyInfo) 45 | { 46 | CakeContext.Information($"Checking whether '{projectRequiringDependency}' is in the list to be processed"); 47 | 48 | // Check dependencies of dependencies 49 | if (knownDependenciesToBeBuilt.Any(x => string.Equals(x, projectRequiringDependency, StringComparison.OrdinalIgnoreCase))) 50 | { 51 | CakeContext.Information($"Dependency '{dependencyProject}' is a dependency of dependency project '{projectRequiringDependency}', including this in the build"); 52 | return true; 53 | } 54 | 55 | // Special case: *if* this is the 2nd round we check, and the project requiring this dependency is a test project, 56 | // we should check whether the test project is not already excluded. If so, the Deploy[SomeProject]Tests will return true 57 | // and this logic will still include it, so we need to exclude it explicitly 58 | if (IsTestProject((BuildContext)ParentContext, projectRequiringDependency) && 59 | !knownDependenciesToBeBuilt.Contains(projectRequiringDependency)) 60 | { 61 | CakeContext.Information($"Dependency '{dependencyProject}' is a dependency of '{projectRequiringDependency}', but that is an already excluded test project, not yet including in the build"); 62 | 63 | // Important: don't return, there might be other projects 64 | continue; 65 | } 66 | 67 | // Check if we should build this project 68 | if (ShouldProcessProject((BuildContext)ParentContext, projectRequiringDependency)) 69 | { 70 | CakeContext.Information($"Dependency '{dependencyProject}' is a dependency of '{projectRequiringDependency}', including this in the build"); 71 | return true; 72 | } 73 | } 74 | 75 | return false; 76 | } 77 | } 78 | 79 | //------------------------------------------------------------- 80 | 81 | private DependenciesContext InitializeDependenciesContext(BuildContext buildContext, IBuildContext parentBuildContext) 82 | { 83 | var data = new DependenciesContext(parentBuildContext, Dependencies); 84 | 85 | return data; 86 | } 87 | 88 | //------------------------------------------------------------- 89 | 90 | Dictionary> _dependencies; 91 | 92 | public Dictionary> Dependencies 93 | { 94 | get 95 | { 96 | if (_dependencies is null) 97 | { 98 | _dependencies = new Dictionary>(); 99 | } 100 | 101 | return _dependencies; 102 | } 103 | } -------------------------------------------------------------------------------- /deployment/cake/docker-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class DockerImagesContext : BuildContextWithItemsBase 6 | { 7 | public DockerImagesContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | public string DockerEngineUrl { get; set; } 13 | public string DockerRegistryUrl { get; set; } 14 | public string DockerRegistryUserName { get; set; } 15 | public string DockerRegistryPassword { get; set; } 16 | 17 | protected override void ValidateContext() 18 | { 19 | } 20 | 21 | protected override void LogStateInfoForContext() 22 | { 23 | CakeContext.Information($"Found '{Items.Count}' docker image projects"); 24 | } 25 | } 26 | 27 | //------------------------------------------------------------- 28 | 29 | private DockerImagesContext InitializeDockerImagesContext(BuildContext buildContext, IBuildContext parentBuildContext) 30 | { 31 | var data = new DockerImagesContext(parentBuildContext) 32 | { 33 | Items = DockerImages ?? new List(), 34 | DockerEngineUrl = buildContext.BuildServer.GetVariable("DockerEngineUrl", showValue: true), 35 | DockerRegistryUrl = buildContext.BuildServer.GetVariable("DockerRegistryUrl", showValue: true), 36 | DockerRegistryUserName = buildContext.BuildServer.GetVariable("DockerRegistryUserName", showValue: false), 37 | DockerRegistryPassword = buildContext.BuildServer.GetVariable("DockerRegistryPassword", showValue: false) 38 | }; 39 | 40 | return data; 41 | } 42 | 43 | //------------------------------------------------------------- 44 | 45 | List _dockerImages; 46 | 47 | public List DockerImages 48 | { 49 | get 50 | { 51 | if (_dockerImages is null) 52 | { 53 | _dockerImages = new List(); 54 | } 55 | 56 | return _dockerImages; 57 | } 58 | } -------------------------------------------------------------------------------- /deployment/cake/github-pages-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class GitHubPagesContext : BuildContextWithItemsBase 6 | { 7 | public GitHubPagesContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | public string RepositoryUrl { get; set; } 13 | public string BranchName { get; set; } 14 | public string Email { get; set; } 15 | public string UserName { get; set; } 16 | public string ApiToken { get; set; } 17 | 18 | protected override void ValidateContext() 19 | { 20 | if (Items.Count == 0) 21 | { 22 | return; 23 | } 24 | 25 | if (string.IsNullOrWhiteSpace(RepositoryUrl)) 26 | { 27 | throw new Exception("GitHubPagesRepositoryUrl must be defined"); 28 | } 29 | 30 | if (string.IsNullOrWhiteSpace(BranchName)) 31 | { 32 | throw new Exception("GitHubPagesBranchName must be defined"); 33 | } 34 | 35 | if (string.IsNullOrWhiteSpace(Email)) 36 | { 37 | throw new Exception("GitHubPagesEmail must be defined"); 38 | } 39 | 40 | if (string.IsNullOrWhiteSpace(UserName)) 41 | { 42 | throw new Exception("GitHubPagesUserName must be defined"); 43 | } 44 | 45 | if (string.IsNullOrWhiteSpace(ApiToken)) 46 | { 47 | throw new Exception("GitHubPagesApiToken must be defined"); 48 | } 49 | } 50 | 51 | protected override void LogStateInfoForContext() 52 | { 53 | CakeContext.Information($"Found '{Items.Count}' GitHub pages projects"); 54 | } 55 | } 56 | 57 | //------------------------------------------------------------- 58 | 59 | private GitHubPagesContext InitializeGitHubPagesContext(BuildContext buildContext, IBuildContext parentBuildContext) 60 | { 61 | var data = new GitHubPagesContext(parentBuildContext) 62 | { 63 | Items = GitHubPages ?? new List(), 64 | RepositoryUrl = buildContext.BuildServer.GetVariable("GitHubPagesRepositoryUrl", ((BuildContext)parentBuildContext).General.Repository.Url, showValue: true), 65 | BranchName = buildContext.BuildServer.GetVariable("GitHubPagesRepositoryUrl", "gh-pages", showValue: true), 66 | Email = buildContext.BuildServer.GetVariable("GitHubPagesEmail", showValue: true), 67 | UserName = buildContext.BuildServer.GetVariable("GitHubPagesUserName", showValue: true), 68 | ApiToken = buildContext.BuildServer.GetVariable("GitHubPagesApiToken", showValue: false), 69 | }; 70 | 71 | return data; 72 | } 73 | 74 | //------------------------------------------------------------- 75 | 76 | List _gitHubPages; 77 | 78 | public List GitHubPages 79 | { 80 | get 81 | { 82 | if (_gitHubPages is null) 83 | { 84 | _gitHubPages = new List(); 85 | } 86 | 87 | return _gitHubPages; 88 | } 89 | } -------------------------------------------------------------------------------- /deployment/cake/installers.cake: -------------------------------------------------------------------------------- 1 | // Customize this file when using a different issue tracker 2 | #l "installers-innosetup.cake" 3 | #l "installers-msix.cake" 4 | #l "installers-squirrel.cake" 5 | #l "installers-velopack.cake" 6 | 7 | using System.Diagnostics; 8 | 9 | //------------------------------------------------------------- 10 | 11 | public interface IInstaller 12 | { 13 | bool IsAvailable { get; } 14 | 15 | Task PackageAsync(string projectName, string channel); 16 | 17 | Task GenerateDeploymentTargetAsync(string projectName); 18 | } 19 | 20 | //------------------------------------------------------------- 21 | 22 | public class DeploymentCatalog 23 | { 24 | public DeploymentCatalog() 25 | { 26 | Targets = new List(); 27 | } 28 | 29 | public List Targets { get; private set; } 30 | } 31 | 32 | //------------------------------------------------------------- 33 | 34 | public class DeploymentTarget 35 | { 36 | public DeploymentTarget() 37 | { 38 | Groups = new List(); 39 | } 40 | 41 | public string Name { get; set; } 42 | 43 | public List Groups { get; private set; } 44 | } 45 | 46 | //------------------------------------------------------------- 47 | 48 | public class DeploymentGroup 49 | { 50 | public DeploymentGroup() 51 | { 52 | Channels = new List(); 53 | } 54 | 55 | public string Name { get; set; } 56 | 57 | public List Channels { get; private set; } 58 | } 59 | 60 | //------------------------------------------------------------- 61 | 62 | public class DeploymentChannel 63 | { 64 | public DeploymentChannel() 65 | { 66 | Releases = new List(); 67 | } 68 | 69 | public string Name { get; set; } 70 | 71 | public List Releases { get; private set; } 72 | } 73 | 74 | //------------------------------------------------------------- 75 | 76 | public class DeploymentRelease 77 | { 78 | public string Name { get; set; } 79 | 80 | public DateTime? Timestamp { get; set;} 81 | 82 | public bool HasFull 83 | { 84 | get { return Full is not null; } 85 | } 86 | 87 | public DeploymentReleasePart Full { get; set; } 88 | 89 | public bool HasDelta 90 | { 91 | get { return Delta is not null; } 92 | } 93 | 94 | public DeploymentReleasePart Delta { get; set; } 95 | } 96 | 97 | //------------------------------------------------------------- 98 | 99 | public class DeploymentReleasePart 100 | { 101 | public string Hash { get; set; } 102 | 103 | public string RelativeFileName { get; set; } 104 | 105 | public ulong Size { get; set; } 106 | } 107 | 108 | //------------------------------------------------------------- 109 | 110 | public class InstallerIntegration : IntegrationBase 111 | { 112 | private readonly List _installers = new List(); 113 | 114 | public InstallerIntegration(BuildContext buildContext) 115 | : base(buildContext) 116 | { 117 | _installers.Add(new InnoSetupInstaller(buildContext)); 118 | _installers.Add(new MsixInstaller(buildContext)); 119 | _installers.Add(new SquirrelInstaller(buildContext)); 120 | _installers.Add(new VelopackInstaller(buildContext)); 121 | } 122 | 123 | public string GetDeploymentChannelSuffix(string prefix = "_", string suffix = "") 124 | { 125 | var channelSuffix = string.Empty; 126 | 127 | if (BuildContext.Wpf.AppendDeploymentChannelSuffix) 128 | { 129 | if (BuildContext.General.IsAlphaBuild || 130 | BuildContext.General.IsBetaBuild) 131 | { 132 | channelSuffix = $"{prefix}{BuildContext.Wpf.Channel}{suffix}"; 133 | } 134 | 135 | BuildContext.CakeContext.Information($"Using deployment channel suffix '{channelSuffix}'"); 136 | } 137 | 138 | return channelSuffix; 139 | } 140 | 141 | public async Task PackageAsync(string projectName, string channel) 142 | { 143 | BuildContext.CakeContext.LogSeparator($"Packaging installer for '{projectName}'"); 144 | 145 | foreach (var installer in _installers) 146 | { 147 | if (!installer.IsAvailable) 148 | { 149 | continue; 150 | } 151 | 152 | BuildContext.CakeContext.LogSeparator($"Applying installer '{installer.GetType().Name}' for '{projectName}'"); 153 | 154 | var stopwatch = Stopwatch.StartNew(); 155 | 156 | try 157 | { 158 | await installer.PackageAsync(projectName, channel); 159 | } 160 | finally 161 | { 162 | stopwatch.Stop(); 163 | 164 | BuildContext.CakeContext.Information($"Installer took {stopwatch.Elapsed}"); 165 | } 166 | } 167 | 168 | if (BuildContext.Wpf.GenerateDeploymentCatalog) 169 | { 170 | BuildContext.CakeContext.LogSeparator($"Generating deployment catalog for '{projectName}'"); 171 | 172 | var catalog = new DeploymentCatalog(); 173 | 174 | foreach (var installer in _installers) 175 | { 176 | if (!installer.IsAvailable) 177 | { 178 | continue; 179 | } 180 | 181 | BuildContext.CakeContext.LogSeparator($"Generating deployment target for catalog for installer '{installer.GetType().Name}' for '{projectName}'"); 182 | 183 | var deploymentTarget = await installer.GenerateDeploymentTargetAsync(projectName); 184 | if (deploymentTarget is not null) 185 | { 186 | catalog.Targets.Add(deploymentTarget); 187 | } 188 | } 189 | 190 | var localCatalogDirectory = System.IO.Path.Combine(BuildContext.General.OutputRootDirectory, "catalog", projectName); 191 | BuildContext.CakeContext.CreateDirectory(localCatalogDirectory); 192 | 193 | var localCatalogFileName = System.IO.Path.Combine(localCatalogDirectory, "catalog.json"); 194 | var json = Newtonsoft.Json.JsonConvert.SerializeObject(catalog); 195 | 196 | System.IO.File.WriteAllText(localCatalogFileName, json); 197 | 198 | if (BuildContext.Wpf.UpdateDeploymentsShare) 199 | { 200 | var targetFileName = System.IO.Path.Combine(BuildContext.Wpf.GetDeploymentShareForProject(projectName), "catalog.json"); 201 | BuildContext.CakeContext.CopyFile(localCatalogFileName, targetFileName); 202 | } 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /deployment/cake/issuetrackers-github.cake: -------------------------------------------------------------------------------- 1 | #tool "nuget:?package=gitreleasemanager&version=0.20.0" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class GitHubIssueTracker : IIssueTracker 6 | { 7 | public GitHubIssueTracker(BuildContext buildContext) 8 | { 9 | BuildContext = buildContext; 10 | 11 | ApiKey = buildContext.BuildServer.GetVariable("GitHubApiKey", showValue: false); 12 | OwnerName = buildContext.BuildServer.GetVariable("GitHubOwnerName", buildContext.General.Copyright.Company, showValue: true); 13 | ProjectName = buildContext.BuildServer.GetVariable("GitHubProjectName", buildContext.General.Solution.Name, showValue: true); 14 | 15 | if (!string.IsNullOrWhiteSpace(ApiKey) && 16 | !string.IsNullOrWhiteSpace(OwnerName) && 17 | !string.IsNullOrWhiteSpace(ProjectName)) 18 | { 19 | IsAvailable = true; 20 | } 21 | } 22 | 23 | public BuildContext BuildContext { get; private set; } 24 | 25 | public string ApiKey { get; set; } 26 | public string OwnerName { get; set; } 27 | public string ProjectName { get; set; } 28 | 29 | public string OwnerAndProjectName 30 | { 31 | get { return $"{OwnerName}/{ProjectName}"; } 32 | } 33 | 34 | public bool IsAvailable { get; private set; } 35 | 36 | public async Task CreateAndReleaseVersionAsync() 37 | { 38 | if (!IsAvailable) 39 | { 40 | BuildContext.CakeContext.Information("GitHub is not available, skipping GitHub integration"); 41 | return; 42 | } 43 | 44 | var version = BuildContext.General.Version.FullSemVer; 45 | 46 | BuildContext.CakeContext.Information("Releasing version '{0}' in GitHub", version); 47 | 48 | // For docs, see https://cakebuild.net/dsl/gitreleasemanager/ 49 | 50 | BuildContext.CakeContext.Information("Step 1 / 4: Creating release"); 51 | 52 | BuildContext.CakeContext.GitReleaseManagerCreate(ApiKey, OwnerName, ProjectName, new GitReleaseManagerCreateSettings 53 | { 54 | TargetDirectory = BuildContext.General.RootDirectory, 55 | Milestone = BuildContext.General.Version.MajorMinorPatch, 56 | Name = version, 57 | Prerelease = !BuildContext.General.IsOfficialBuild, 58 | TargetCommitish = BuildContext.General.Repository.CommitId 59 | }); 60 | 61 | BuildContext.CakeContext.Information("Step 2 / 4: Adding assets to the release (not supported yet)"); 62 | 63 | // Not yet supported 64 | 65 | if (!BuildContext.General.IsOfficialBuild) 66 | { 67 | BuildContext.CakeContext.Information("GitHub release publishing only runs against non-prerelease builds"); 68 | } 69 | else 70 | { 71 | BuildContext.CakeContext.Information("Step 3 / 4: Publishing release"); 72 | 73 | BuildContext.CakeContext.GitReleaseManagerPublish(ApiKey, OwnerName, ProjectName, BuildContext.General.Version.MajorMinorPatch, new GitReleaseManagerPublishSettings 74 | { 75 | TargetDirectory = BuildContext.General.RootDirectory 76 | }); 77 | 78 | BuildContext.CakeContext.Information("Step 4 / 4: Closing the milestone"); 79 | 80 | BuildContext.CakeContext.GitReleaseManagerClose(ApiKey, OwnerName, ProjectName, BuildContext.General.Version.MajorMinorPatch, new GitReleaseManagerCloseMilestoneSettings 81 | { 82 | TargetDirectory = BuildContext.General.RootDirectory 83 | }); 84 | } 85 | 86 | BuildContext.CakeContext.Information("Released version in GitHub"); 87 | } 88 | } -------------------------------------------------------------------------------- /deployment/cake/issuetrackers-jira.cake: -------------------------------------------------------------------------------- 1 | #tool "nuget:?package=JiraCli&version=1.3.0-alpha0338&prerelease" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class JiraIssueTracker : IIssueTracker 6 | { 7 | public JiraIssueTracker(BuildContext buildContext) 8 | { 9 | BuildContext = buildContext; 10 | 11 | Url = buildContext.BuildServer.GetVariable("JiraUrl", showValue: true); 12 | Username = buildContext.BuildServer.GetVariable("JiraUsername", showValue: true); 13 | Password = buildContext.BuildServer.GetVariable("JiraPassword", showValue: false); 14 | ProjectName = buildContext.BuildServer.GetVariable("JiraProjectName", showValue: true); 15 | 16 | if (!string.IsNullOrWhiteSpace(Url) && 17 | !string.IsNullOrWhiteSpace(ProjectName)) 18 | { 19 | IsAvailable = true; 20 | } 21 | } 22 | 23 | public BuildContext BuildContext { get; private set; } 24 | 25 | public string Url { get; set; } 26 | public string Username { get; set; } 27 | public string Password { get; set; } 28 | public string ProjectName { get; set; } 29 | public bool IsAvailable { get; private set; } 30 | 31 | public async Task CreateAndReleaseVersionAsync() 32 | { 33 | if (!IsAvailable) 34 | { 35 | BuildContext.CakeContext.Information("JIRA is not available, skipping JIRA integration"); 36 | return; 37 | } 38 | 39 | var version = BuildContext.General.Version.FullSemVer; 40 | 41 | BuildContext.CakeContext.Information("Releasing version '{0}' in JIRA", version); 42 | 43 | // Example call: 44 | // JiraCli.exe -url %JiraUrl% -user %JiraUsername% -pw %JiraPassword% -action createandreleaseversion 45 | // -project %JiraProjectName% -version %GitVersion_FullSemVer% -merge %IsOfficialBuild% 46 | 47 | var nugetPath = BuildContext.CakeContext.Tools.Resolve("JiraCli.exe"); 48 | BuildContext.CakeContext.StartProcess(nugetPath, new ProcessSettings 49 | { 50 | Arguments = new ProcessArgumentBuilder() 51 | .AppendSwitch("-url", Url) 52 | .AppendSwitch("-user", Username) 53 | .AppendSwitchSecret("-pw", Password) 54 | .AppendSwitch("-action", "createandreleaseversion") 55 | .AppendSwitch("-project", ProjectName) 56 | .AppendSwitch("-version", version) 57 | .AppendSwitch("-merge", BuildContext.General.IsOfficialBuild.ToString()) 58 | }); 59 | 60 | BuildContext.CakeContext.Information("Released version in JIRA"); 61 | } 62 | } -------------------------------------------------------------------------------- /deployment/cake/issuetrackers.cake: -------------------------------------------------------------------------------- 1 | // Customize this file when using a different issue tracker 2 | #l "issuetrackers-github.cake" 3 | #l "issuetrackers-jira.cake" 4 | 5 | //------------------------------------------------------------- 6 | 7 | public interface IIssueTracker 8 | { 9 | Task CreateAndReleaseVersionAsync(); 10 | } 11 | 12 | //------------------------------------------------------------- 13 | 14 | public class IssueTrackerIntegration : IntegrationBase 15 | { 16 | private readonly List _issueTrackers = new List(); 17 | 18 | public IssueTrackerIntegration(BuildContext buildContext) 19 | : base(buildContext) 20 | { 21 | _issueTrackers.Add(new GitHubIssueTracker(buildContext)); 22 | _issueTrackers.Add(new JiraIssueTracker(buildContext)); 23 | } 24 | 25 | public async Task CreateAndReleaseVersionAsync() 26 | { 27 | BuildContext.CakeContext.LogSeparator("Creating and releasing version"); 28 | 29 | foreach (var issueTracker in _issueTrackers) 30 | { 31 | try 32 | { 33 | await issueTracker.CreateAndReleaseVersionAsync(); 34 | } 35 | catch (Exception ex) 36 | { 37 | BuildContext.CakeContext.Error(ex.Message); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /deployment/cake/lib-logging.cake: -------------------------------------------------------------------------------- 1 | // Note: code originally comes from https://stackoverflow.com/questions/50826394/how-to-print-tool-command-line-in-cake 2 | 3 | /// 4 | /// Temporary sets logging verbosity. 5 | /// 6 | /// 7 | /// 8 | /// // Temporary sets logging verbosity to Diagnostic. 9 | /// using(context.UseVerbosity(Verbosity.Diagnostic)) 10 | /// { 11 | /// context.DotNetBuild(project, settings); 12 | /// } 13 | /// 14 | /// 15 | public static VerbosityChanger UseVerbosity(this ICakeContext context, Verbosity newVerbosity) => 16 | new VerbosityChanger(context.Log, newVerbosity); 17 | 18 | 19 | /// 20 | /// Temporary sets logging verbosity to Diagnostic. 21 | /// 22 | /// 23 | /// 24 | /// // Temporary sets logging verbosity to Diagnostic. 25 | /// using(context.UseDiagnosticVerbosity()) 26 | /// { 27 | /// context.DotNetBuild(project, settings); 28 | /// } 29 | /// 30 | /// 31 | public static VerbosityChanger UseDiagnosticVerbosity(this ICakeContext context) => 32 | context.UseVerbosity(Verbosity.Diagnostic); 33 | 34 | /// 35 | /// Cake log verbosity changer. 36 | /// Restores old verbosity on Dispose. 37 | /// 38 | public class VerbosityChanger : IDisposable 39 | { 40 | ICakeLog _log; 41 | Verbosity _oldVerbosity; 42 | 43 | public VerbosityChanger(ICakeLog log, Verbosity newVerbosity) 44 | { 45 | _log = log; 46 | _oldVerbosity = log.Verbosity; 47 | _log.Verbosity = newVerbosity; 48 | } 49 | 50 | public void Dispose() => _log.Verbosity = _oldVerbosity; 51 | } -------------------------------------------------------------------------------- /deployment/cake/lib-nuget.cake: -------------------------------------------------------------------------------- 1 | public class NuGetServer 2 | { 3 | public string Url { get;set; } 4 | 5 | public string ApiKey { get;set; } 6 | 7 | public override string ToString() 8 | { 9 | var result = Url; 10 | 11 | result += string.Format(" (ApiKey present: '{0}')", !string.IsNullOrWhiteSpace(ApiKey)); 12 | 13 | return result; 14 | } 15 | } 16 | 17 | //------------------------------------------------------------- 18 | 19 | public static List GetNuGetServers(string urls, string apiKeys) 20 | { 21 | var splittedUrls = urls.Split(new [] { ";" }, StringSplitOptions.None); 22 | var splittedApiKeys = apiKeys.Split(new [] { ";" }, StringSplitOptions.None); 23 | 24 | if (splittedUrls.Length != splittedApiKeys.Length) 25 | { 26 | throw new Exception("Number of api keys does not match number of urls. Even if an API key is not required, add an empty one"); 27 | } 28 | 29 | var servers = new List(); 30 | 31 | for (int i = 0; i < splittedUrls.Length; i++) 32 | { 33 | var url = splittedUrls[i]; 34 | if (string.IsNullOrWhiteSpace(url)) 35 | { 36 | throw new Exception("Url for NuGet server cannot be empty"); 37 | } 38 | 39 | servers.Add(new NuGetServer 40 | { 41 | Url = url, 42 | ApiKey = splittedApiKeys[i] 43 | }); 44 | } 45 | 46 | return servers; 47 | } 48 | 49 | //------------------------------------------------------------- 50 | 51 | private static void RestoreNuGetPackages(BuildContext buildContext, Cake.Core.IO.FilePath solutionOrProjectFileName) 52 | { 53 | buildContext.CakeContext.LogSeparator("Restoring packages for '{0}'", solutionOrProjectFileName); 54 | 55 | var sources = SplitSeparatedList(buildContext.General.NuGet.PackageSources, ';'); 56 | 57 | var runtimeIdentifiers = new [] 58 | { 59 | "win-x86", 60 | "win-x64", 61 | "win-arm64", 62 | "browser-wasm" 63 | }; 64 | 65 | var supportedRuntimeIdentifiers = GetProjectRuntimesIdentifiers(buildContext, solutionOrProjectFileName, runtimeIdentifiers); 66 | 67 | RestoreNuGetPackagesUsingNuGet(buildContext, solutionOrProjectFileName, sources, supportedRuntimeIdentifiers); 68 | RestoreNuGetPackagesUsingDotnetRestore(buildContext, solutionOrProjectFileName, sources, supportedRuntimeIdentifiers); 69 | } 70 | 71 | //------------------------------------------------------------- 72 | 73 | private static void RestoreNuGetPackagesUsingNuGet(BuildContext buildContext, Cake.Core.IO.FilePath solutionOrProjectFileName, IReadOnlyList sources, IReadOnlyList runtimeIdentifiers) 74 | { 75 | if (!buildContext.General.NuGet.RestoreUsingNuGet) 76 | { 77 | return; 78 | } 79 | 80 | buildContext.CakeContext.LogSeparator("Restoring packages for '{0}' using 'NuGet'", solutionOrProjectFileName); 81 | 82 | // No need to deal with runtime identifiers 83 | 84 | try 85 | { 86 | var nuGetRestoreSettings = new NuGetRestoreSettings 87 | { 88 | DisableParallelProcessing = false, 89 | NoCache = false, 90 | NonInteractive = true, 91 | RequireConsent = false 92 | }; 93 | 94 | if (sources.Count > 0) 95 | { 96 | nuGetRestoreSettings.Source = sources.ToList(); 97 | } 98 | 99 | buildContext.CakeContext.NuGetRestore(solutionOrProjectFileName, nuGetRestoreSettings); 100 | } 101 | catch (Exception) 102 | { 103 | // Ignore 104 | } 105 | } 106 | 107 | //------------------------------------------------------------- 108 | 109 | private static void RestoreNuGetPackagesUsingDotnetRestore(BuildContext buildContext, Cake.Core.IO.FilePath solutionOrProjectFileName, IReadOnlyList sources, IReadOnlyList runtimeIdentifiers) 110 | { 111 | if (!buildContext.General.NuGet.RestoreUsingDotNetRestore) 112 | { 113 | return; 114 | } 115 | 116 | buildContext.CakeContext.LogSeparator("Restoring packages for '{0}' using 'dotnet restore'", solutionOrProjectFileName); 117 | 118 | foreach (var runtimeIdentifier in runtimeIdentifiers) 119 | { 120 | try 121 | { 122 | buildContext.CakeContext.LogSeparator("Restoring packages for '{0}' using 'dotnet restore' using runtime identifier '{1}'", solutionOrProjectFileName, runtimeIdentifier); 123 | 124 | var restoreSettings = new DotNetRestoreSettings 125 | { 126 | DisableParallel = false, 127 | Force = false, 128 | ForceEvaluate = false, 129 | IgnoreFailedSources = true, 130 | NoCache = false, 131 | NoDependencies = buildContext.General.NuGet.NoDependencies, // use true to speed up things 132 | Verbosity = DotNetVerbosity.Normal 133 | }; 134 | 135 | if (!string.IsNullOrWhiteSpace(runtimeIdentifier)) 136 | { 137 | buildContext.CakeContext.Information("Project restore uses explicit runtime identifier, forcing re-evaluation"); 138 | 139 | restoreSettings.Force = true; 140 | restoreSettings.ForceEvaluate = true; 141 | restoreSettings.Runtime = runtimeIdentifier; 142 | } 143 | 144 | if (sources.Count > 0) 145 | { 146 | restoreSettings.Sources = sources.ToList(); 147 | } 148 | 149 | using (buildContext.CakeContext.UseDiagnosticVerbosity()) 150 | { 151 | buildContext.CakeContext.DotNetRestore(solutionOrProjectFileName.FullPath, restoreSettings); 152 | } 153 | } 154 | catch (Exception) 155 | { 156 | // Ignore 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /deployment/cake/lib-sourcelink.cake: -------------------------------------------------------------------------------- 1 | public static bool IsSourceLinkSupported(BuildContext buildContext, string projectName, string projectFileName) 2 | { 3 | if (buildContext.General.SourceLink.IsDisabled) 4 | { 5 | return false; 6 | } 7 | 8 | if (buildContext.General.IsLocalBuild) 9 | { 10 | return false; 11 | } 12 | 13 | if (string.IsNullOrWhiteSpace(buildContext.General.Repository.Url)) 14 | { 15 | return false; 16 | } 17 | 18 | // Only support C# projects 19 | if (!projectFileName.EndsWith(".csproj")) 20 | { 21 | return false; 22 | } 23 | 24 | // Is this a test project? 25 | if (buildContext.Tests.Items.Contains(projectName)) 26 | { 27 | return false; 28 | } 29 | 30 | // Only support when running a real build, e.g. ot for 'Package' only 31 | if (!buildContext.General.Target.ToLower().Contains("build")) 32 | { 33 | return false; 34 | } 35 | 36 | return true; 37 | } 38 | 39 | //------------------------------------------------------------- 40 | 41 | public static void InjectSourceLinkInProjectFile(BuildContext buildContext, string projectName, string projectFileName) 42 | { 43 | try 44 | { 45 | // Only support C# projects 46 | if (!IsSourceLinkSupported(buildContext, projectName, projectFileName)) 47 | { 48 | return; 49 | } 50 | 51 | // For SourceLink to work, the .csproj should contain something like this: 52 | // 53 | var projectFileContents = System.IO.File.ReadAllText(projectFileName); 54 | if (projectFileContents.Contains("Microsoft.SourceLink.GitHub")) 55 | { 56 | return; 57 | } 58 | 59 | buildContext.CakeContext.Warning("No SourceLink reference found, automatically injecting SourceLink package reference now"); 60 | 61 | //const string MSBuildNS = (XNamespace) "http://schemas.microsoft.com/developer/msbuild/2003"; 62 | 63 | var xmlDocument = XDocument.Parse(projectFileContents); 64 | var projectElement = xmlDocument.Root; 65 | 66 | // Item group with package reference 67 | var referencesItemGroup = new XElement("ItemGroup"); 68 | var sourceLinkPackageReference = new XElement("PackageReference"); 69 | sourceLinkPackageReference.Add(new XAttribute("Include", "Microsoft.SourceLink.GitHub")); 70 | sourceLinkPackageReference.Add(new XAttribute("Version", "1.1.1")); 71 | sourceLinkPackageReference.Add(new XAttribute("PrivateAssets", "all")); 72 | 73 | referencesItemGroup.Add(sourceLinkPackageReference); 74 | projectElement.Add(referencesItemGroup); 75 | 76 | // Item group with source root 77 | // 78 | var sourceRootItemGroup = new XElement("ItemGroup"); 79 | var sourceRoot = new XElement("SourceRoot"); 80 | 81 | // Required to end with a \ 82 | var sourceRootValue = buildContext.General.RootDirectory; 83 | var directorySeparator = System.IO.Path.DirectorySeparatorChar.ToString(); 84 | if (!sourceRootValue.EndsWith(directorySeparator)) 85 | { 86 | sourceRootValue += directorySeparator; 87 | }; 88 | 89 | sourceRoot.Add(new XAttribute("Include", sourceRootValue)); 90 | sourceRoot.Add(new XAttribute("RepositoryUrl", buildContext.General.Repository.Url)); 91 | 92 | // Note: since we are not allowing source control manager queries (we don't want to require a .git directory), 93 | // we must specify the additional information below 94 | sourceRoot.Add(new XAttribute("SourceControl", "git")); 95 | sourceRoot.Add(new XAttribute("RevisionId", buildContext.General.Repository.CommitId)); 96 | 97 | sourceRootItemGroup.Add(sourceRoot); 98 | projectElement.Add(sourceRootItemGroup); 99 | 100 | xmlDocument.Save(projectFileName); 101 | } 102 | catch (Exception ex) 103 | { 104 | buildContext.CakeContext.Error($"Failed to process source link for project '{projectFileName}': {ex.Message}"); 105 | } 106 | } -------------------------------------------------------------------------------- /deployment/cake/notifications-msteams.cake: -------------------------------------------------------------------------------- 1 | #addin "nuget:?package=Cake.MicrosoftTeams&version=2.0.0" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class MsTeamsNotifier : INotifier 6 | { 7 | public MsTeamsNotifier(BuildContext buildContext) 8 | { 9 | BuildContext = buildContext; 10 | 11 | WebhookUrl = buildContext.BuildServer.GetVariable("MsTeamsWebhookUrl", showValue: false); 12 | WebhookUrlForErrors = buildContext.BuildServer.GetVariable("MsTeamsWebhookUrlForErrors", WebhookUrl, showValue: false); 13 | } 14 | 15 | public BuildContext BuildContext { get; private set; } 16 | 17 | public string WebhookUrl { get; private set; } 18 | public string WebhookUrlForErrors { get; private set; } 19 | 20 | public string GetMsTeamsWebhookUrl(string project, TargetType targetType) 21 | { 22 | // Allow per target overrides via "MsTeamsWebhookUrlFor[TargetType]" 23 | var targetTypeUrl = GetTargetSpecificConfigurationValue(BuildContext, targetType, "MsTeamsWebhookUrlFor", string.Empty); 24 | if (!string.IsNullOrEmpty(targetTypeUrl)) 25 | { 26 | return targetTypeUrl; 27 | } 28 | 29 | // Allow per project overrides via "MsTeamsWebhookUrlFor[ProjectName]" 30 | var projectTypeUrl = GetProjectSpecificConfigurationValue(BuildContext, project, "MsTeamsWebhookUrlFor", string.Empty); 31 | if (!string.IsNullOrEmpty(projectTypeUrl)) 32 | { 33 | return projectTypeUrl; 34 | } 35 | 36 | // Return default fallback 37 | return WebhookUrl; 38 | } 39 | 40 | //------------------------------------------------------------- 41 | 42 | private string GetMsTeamsTarget(string project, TargetType targetType, NotificationType notificationType) 43 | { 44 | if (notificationType == NotificationType.Error) 45 | { 46 | return WebhookUrlForErrors; 47 | } 48 | 49 | return GetMsTeamsWebhookUrl(project, targetType); 50 | } 51 | 52 | //------------------------------------------------------------- 53 | 54 | public async Task NotifyAsync(string project, string message, TargetType targetType, NotificationType notificationType) 55 | { 56 | var targetWebhookUrl = GetMsTeamsTarget(project, targetType, notificationType); 57 | if (string.IsNullOrWhiteSpace(targetWebhookUrl)) 58 | { 59 | return; 60 | } 61 | 62 | var messageCard = new MicrosoftTeamsMessageCard 63 | { 64 | title = project, 65 | summary = notificationType.ToString(), 66 | sections = new [] 67 | { 68 | new MicrosoftTeamsMessageSection 69 | { 70 | activityTitle = notificationType.ToString(), 71 | activitySubtitle = message, 72 | activityText = " ", 73 | activityImage = "https://raw.githubusercontent.com/cake-build/graphics/master/png/cake-small.png", 74 | facts = new [] 75 | { 76 | new MicrosoftTeamsMessageFacts { name ="Project", value = project }, 77 | new MicrosoftTeamsMessageFacts { name ="Version", value = BuildContext.General.Version.FullSemVer }, 78 | new MicrosoftTeamsMessageFacts { name ="CakeVersion", value = BuildContext.CakeContext.Environment.Runtime.CakeVersion.ToString() }, 79 | //new MicrosoftTeamsMessageFacts { name ="TargetFramework", value = Context.Environment.Runtime .TargetFramework.ToString() }, 80 | }, 81 | } 82 | } 83 | }; 84 | 85 | var result = BuildContext.CakeContext.MicrosoftTeamsPostMessage(messageCard, new MicrosoftTeamsSettings 86 | { 87 | IncomingWebhookUrl = targetWebhookUrl 88 | }); 89 | 90 | if (result != System.Net.HttpStatusCode.OK) 91 | { 92 | BuildContext.CakeContext.Warning(string.Format("MsTeams result: {0}", result)); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /deployment/cake/notifications.cake: -------------------------------------------------------------------------------- 1 | #l "notifications-msteams.cake" 2 | //#l "notifications-slack.cake" 3 | 4 | //------------------------------------------------------------- 5 | 6 | public enum NotificationType 7 | { 8 | Info, 9 | 10 | Error 11 | } 12 | 13 | //------------------------------------------------------------- 14 | 15 | public interface INotifier 16 | { 17 | Task NotifyAsync(string project, string message, TargetType targetType = TargetType.Unknown, NotificationType notificationType = NotificationType.Info); 18 | } 19 | 20 | //------------------------------------------------------------- 21 | 22 | public class NotificationsIntegration : IntegrationBase 23 | { 24 | private readonly List _notifiers = new List(); 25 | 26 | public NotificationsIntegration(BuildContext buildContext) 27 | : base(buildContext) 28 | { 29 | _notifiers.Add(new MsTeamsNotifier(buildContext)); 30 | } 31 | 32 | public async Task NotifyDefaultAsync(string project, string message, TargetType targetType = TargetType.Unknown) 33 | { 34 | await NotifyAsync(project, message, targetType, NotificationType.Info); 35 | } 36 | 37 | //------------------------------------------------------------- 38 | 39 | public async Task NotifyErrorAsync(string project, string message, TargetType targetType = TargetType.Unknown) 40 | { 41 | await NotifyAsync(project, string.Format("ERROR: {0}", message), targetType, NotificationType.Error); 42 | } 43 | 44 | //------------------------------------------------------------- 45 | 46 | public async Task NotifyAsync(string project, string message, TargetType targetType = TargetType.Unknown, NotificationType notificationType = NotificationType.Info) 47 | { 48 | foreach (var notifier in _notifiers) 49 | { 50 | await notifier.NotifyAsync(project, message, targetType, notificationType); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /deployment/cake/sourcecontrol-github.cake: -------------------------------------------------------------------------------- 1 | #addin "nuget:?package=Cake.GitHub&version=1.0.0" 2 | #addin "nuget:?package=Octokit&version=14.0.0" 3 | 4 | //------------------------------------------------------------- 5 | 6 | public class GitHubSourceControl : ISourceControl 7 | { 8 | public GitHubSourceControl(BuildContext buildContext) 9 | { 10 | BuildContext = buildContext; 11 | 12 | ApiKey = buildContext.BuildServer.GetVariable("GitHubApiKey", buildContext.General.Repository.Password, showValue: false); 13 | OwnerName = buildContext.BuildServer.GetVariable("GitHubOwnerName", buildContext.General.Copyright.Company, showValue: true); 14 | ProjectName = buildContext.BuildServer.GetVariable("GitHubProjectName", buildContext.General.Solution.Name, showValue: true); 15 | 16 | if (!string.IsNullOrWhiteSpace(ApiKey) && 17 | !string.IsNullOrWhiteSpace(OwnerName) && 18 | !string.IsNullOrWhiteSpace(ProjectName)) 19 | { 20 | IsAvailable = true; 21 | } 22 | } 23 | 24 | public BuildContext BuildContext { get; private set; } 25 | 26 | public string ApiKey { get; set; } 27 | public string OwnerName { get; set; } 28 | public string ProjectName { get; set; } 29 | 30 | public string OwnerAndProjectName 31 | { 32 | get { return $"{OwnerName}/{ProjectName}"; } 33 | } 34 | 35 | public bool IsAvailable { get; private set; } 36 | 37 | public async Task MarkBuildAsPendingAsync(string context, string description) 38 | { 39 | UpdateStatus(GitHubStatusState.Pending, context, description); 40 | } 41 | 42 | public async Task MarkBuildAsFailedAsync(string context, string description) 43 | { 44 | UpdateStatus(GitHubStatusState.Failure, context, description); 45 | } 46 | 47 | public async Task MarkBuildAsSucceededAsync(string context, string description) 48 | { 49 | UpdateStatus(GitHubStatusState.Success, context, description); 50 | } 51 | 52 | private void UpdateStatus(GitHubStatusState state, string context, string description) 53 | { 54 | // Disabled for now 55 | return; 56 | 57 | if (!IsAvailable) 58 | { 59 | return; 60 | } 61 | 62 | BuildContext.CakeContext.Information("Updating GitHub status to '{0}' | '{1}'", state, description); 63 | 64 | var commitSha = BuildContext.General.Repository.CommitId; 65 | 66 | // Note: UserName is not really required, use string.Empty, then only api key is needed 67 | BuildContext.CakeContext.GitHubStatus(string.Empty, ApiKey, OwnerName, ProjectName, commitSha, new GitHubStatusSettings 68 | { 69 | State = state, 70 | TargetUrl = null,// "url-to-build-server", 71 | Description = description, 72 | Context = $"Cake - {context}" 73 | }); 74 | } 75 | } -------------------------------------------------------------------------------- /deployment/cake/sourcecontrol.cake: -------------------------------------------------------------------------------- 1 | // Customize this file when using a different source controls 2 | #l "sourcecontrol-github.cake" 3 | 4 | //------------------------------------------------------------- 5 | 6 | public interface ISourceControl 7 | { 8 | Task MarkBuildAsPendingAsync(string context, string description); 9 | Task MarkBuildAsFailedAsync(string context, string description); 10 | Task MarkBuildAsSucceededAsync(string context, string description); 11 | } 12 | 13 | //------------------------------------------------------------- 14 | 15 | public class SourceControlIntegration : IntegrationBase 16 | { 17 | private readonly List _sourceControls = new List(); 18 | 19 | public SourceControlIntegration(BuildContext buildContext) 20 | : base(buildContext) 21 | { 22 | _sourceControls.Add(new GitHubSourceControl(buildContext)); 23 | } 24 | 25 | public async Task MarkBuildAsPendingAsync(string context, string description = null) 26 | { 27 | BuildContext.CakeContext.LogSeparator($"Marking build as pending: '{description ?? string.Empty}'"); 28 | 29 | context = context ?? "default"; 30 | description = description ?? "Build pending"; 31 | 32 | foreach (var sourceControl in _sourceControls) 33 | { 34 | try 35 | { 36 | await sourceControl.MarkBuildAsPendingAsync(context, description); 37 | } 38 | catch (Exception ex) 39 | { 40 | BuildContext.CakeContext.Warning($"Failed to update status: {ex.Message}"); 41 | } 42 | } 43 | } 44 | 45 | public async Task MarkBuildAsFailedAsync(string context, string description = null) 46 | { 47 | BuildContext.CakeContext.LogSeparator($"Marking build as failed: '{description ?? string.Empty}'"); 48 | 49 | context = context ?? "default"; 50 | description = description ?? "Build failed"; 51 | 52 | foreach (var sourceControl in _sourceControls) 53 | { 54 | try 55 | { 56 | await sourceControl.MarkBuildAsFailedAsync(context, description); 57 | } 58 | catch (Exception ex) 59 | { 60 | BuildContext.CakeContext.Warning($"Failed to update status: {ex.Message}"); 61 | } 62 | } 63 | } 64 | 65 | public async Task MarkBuildAsSucceededAsync(string context, string description = null) 66 | { 67 | BuildContext.CakeContext.LogSeparator($"Marking build as succeeded: '{description ?? string.Empty}'"); 68 | 69 | context = context ?? "default"; 70 | description = description ?? "Build succeeded"; 71 | 72 | foreach (var sourceControl in _sourceControls) 73 | { 74 | try 75 | { 76 | await sourceControl.MarkBuildAsSucceededAsync(context, description); 77 | } 78 | catch (Exception ex) 79 | { 80 | BuildContext.CakeContext.Warning($"Failed to update status: {ex.Message}"); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /deployment/cake/templates-tasks.cake: -------------------------------------------------------------------------------- 1 | #l "templates-variables.cake" 2 | 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using System.Xml.Linq; 6 | using System.IO; 7 | 8 | //------------------------------------------------------------- 9 | 10 | public class TemplatesProcessor : ProcessorBase 11 | { 12 | public TemplatesProcessor(BuildContext buildContext) 13 | : base(buildContext) 14 | { 15 | var templatesRelativePath = "./deployment/templates"; 16 | 17 | if (CakeContext.DirectoryExists(templatesRelativePath)) 18 | { 19 | var currentDirectoryPath = System.IO.Directory.GetCurrentDirectory(); 20 | var templateAbsolutePath = System.IO.Path.Combine(currentDirectoryPath, templatesRelativePath); 21 | var files = System.IO.Directory.GetFiles(templateAbsolutePath, "*.*", System.IO.SearchOption.AllDirectories); 22 | 23 | CakeContext.Information($"Found '{files.Count()}' template files"); 24 | 25 | foreach (var file in files) 26 | { 27 | BuildContext.Templates.Items.Add(file.Substring(templateAbsolutePath.Length + 1)); 28 | } 29 | } 30 | } 31 | 32 | public override bool HasItems() 33 | { 34 | return BuildContext.Templates.Items.Count > 0; 35 | } 36 | 37 | public override async Task PrepareAsync() 38 | { 39 | 40 | } 41 | 42 | public override async Task UpdateInfoAsync() 43 | { 44 | if (!HasItems()) 45 | { 46 | return; 47 | } 48 | 49 | var variableRegex = new Regex(@"\$\{([^}]+)\}", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled); 50 | 51 | foreach (var template in BuildContext.Templates.Items) 52 | { 53 | CakeContext.Information($"Updating template file '{template}'"); 54 | 55 | var templateSourceFile = $"./deployment/templates/{template}"; 56 | var content = CakeContext.FileReadText(templateSourceFile); 57 | 58 | var matches = variableRegex.Matches(content); 59 | 60 | foreach (var match in matches.Cast()) 61 | { 62 | var variableName = match.Groups[1].Value; 63 | 64 | CakeContext.Information($"Found usage of variable '{variableName}'"); 65 | 66 | if (!BuildContext.Variables.TryGetValue(variableName, out var replacement)) 67 | { 68 | CakeContext.Error($"Could not find value for variable '{variableName}'"); 69 | continue; 70 | } 71 | 72 | content = content.Replace($"${{{variableName}}}", replacement); 73 | } 74 | 75 | CakeContext.FileWriteText($"{template}", content); 76 | } 77 | } 78 | 79 | public override async Task BuildAsync() 80 | { 81 | // Run templates every time 82 | await UpdateInfoAsync(); 83 | } 84 | 85 | public override async Task PackageAsync() 86 | { 87 | // Run templates every time 88 | await UpdateInfoAsync(); 89 | } 90 | 91 | public override async Task DeployAsync() 92 | { 93 | if (!HasItems()) 94 | { 95 | return; 96 | } 97 | } 98 | 99 | public override async Task FinalizeAsync() 100 | { 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /deployment/cake/templates-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class TemplatesContext : BuildContextWithItemsBase 6 | { 7 | public TemplatesContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | protected override void ValidateContext() 13 | { 14 | 15 | } 16 | 17 | protected override void LogStateInfoForContext() 18 | { 19 | CakeContext.Information($"Found '{Items.Count}' template items"); 20 | } 21 | } 22 | 23 | //------------------------------------------------------------- 24 | 25 | private TemplatesContext InitializeTemplatesContext(BuildContext buildContext, IBuildContext parentBuildContext) 26 | { 27 | var data = new TemplatesContext(parentBuildContext) 28 | { 29 | Items = Templates ?? new List(), 30 | }; 31 | 32 | return data; 33 | } 34 | 35 | //------------------------------------------------------------- 36 | 37 | List _templates; 38 | 39 | public List Templates 40 | { 41 | get 42 | { 43 | if (_templates is null) 44 | { 45 | _templates = new List(); 46 | } 47 | 48 | return _templates; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /deployment/cake/tests-nunit.cake: -------------------------------------------------------------------------------- 1 | #tool "nuget:?package=NUnit.ConsoleRunner&version=3.20.1" 2 | 3 | //------------------------------------------------------------- 4 | 5 | private static void RunTestsUsingNUnit(BuildContext buildContext, string projectName, string testTargetFramework, string testResultsDirectory) 6 | { 7 | var testFile = System.IO.Path.Combine(GetProjectOutputDirectory(buildContext, projectName), 8 | testTargetFramework, $"{projectName}.dll"); 9 | var resultsFile = System.IO.Path.Combine(testResultsDirectory, "testresults.xml"); 10 | 11 | var nunitSettings = new NUnit3Settings 12 | { 13 | Results = new NUnit3Result[] 14 | { 15 | new NUnit3Result 16 | { 17 | FileName = resultsFile, 18 | Format = "nunit3" 19 | } 20 | }, 21 | NoHeader = true, 22 | NoColor = true, 23 | NoResults = false, 24 | X86 = string.Equals(buildContext.Tests.ProcessBit, "X86", StringComparison.OrdinalIgnoreCase), 25 | Timeout = 60 * 1000, // 60 seconds 26 | Workers = 1 27 | //Work = testResultsDirectory 28 | }; 29 | 30 | // Note: although the docs say you can use without array initialization, you can't 31 | buildContext.CakeContext.NUnit3(new string[] { testFile }, nunitSettings); 32 | 33 | buildContext.CakeContext.Information("Verifying whether results file '{0}' exists", resultsFile); 34 | 35 | if (!buildContext.CakeContext.FileExists(resultsFile)) 36 | { 37 | throw new Exception(string.Format("Expected results file '{0}' does not exist", resultsFile)); 38 | } 39 | } -------------------------------------------------------------------------------- /deployment/cake/tests-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class TestsContext : BuildContextWithItemsBase 6 | { 7 | public TestsContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | public string Framework { get; set; } 13 | public string TargetFramework { get; set; } 14 | public string OperatingSystem { get; set; } 15 | public string ProcessBit { get; set; } 16 | 17 | protected override void ValidateContext() 18 | { 19 | if (Items.Count == 0) 20 | { 21 | return; 22 | } 23 | 24 | if (string.IsNullOrWhiteSpace(Framework)) 25 | { 26 | throw new Exception("Framework is required, specify via 'TestFramework'"); 27 | } 28 | 29 | if (string.IsNullOrWhiteSpace(ProcessBit)) 30 | { 31 | throw new Exception("ProcessBit is required, specify via 'TestProcessBit'"); 32 | } 33 | } 34 | 35 | protected override void LogStateInfoForContext() 36 | { 37 | CakeContext.Information($"Found '{Items.Count}' test projects"); 38 | } 39 | } 40 | 41 | //------------------------------------------------------------- 42 | 43 | private TestsContext InitializeTestsContext(BuildContext buildContext, IBuildContext parentBuildContext) 44 | { 45 | var data = new TestsContext(parentBuildContext) 46 | { 47 | Items = TestProjects, 48 | 49 | Framework = buildContext.BuildServer.GetVariable("TestFramework", "nunit", showValue: true), 50 | TargetFramework = buildContext.BuildServer.GetVariable("TestTargetFramework", "", showValue: true), 51 | OperatingSystem = buildContext.BuildServer.GetVariable("TestOperatingSystem", "win", showValue: true), 52 | ProcessBit = buildContext.BuildServer.GetVariable("TestProcessBit", "X64", showValue: true) 53 | }; 54 | 55 | return data; 56 | } 57 | 58 | //------------------------------------------------------------- 59 | 60 | List _testProjects; 61 | 62 | public List TestProjects 63 | { 64 | get 65 | { 66 | if (_testProjects is null) 67 | { 68 | _testProjects = new List(); 69 | } 70 | 71 | return _testProjects; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /deployment/cake/tools-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | //------------------------------------------------------------- 4 | 5 | public class ToolsContext : BuildContextWithItemsBase 6 | { 7 | public ToolsContext(IBuildContext parentBuildContext) 8 | : base(parentBuildContext) 9 | { 10 | } 11 | 12 | public string NuGetRepositoryUrls { get; set; } 13 | public string NuGetRepositoryApiKeys { get; set; } 14 | 15 | protected override void ValidateContext() 16 | { 17 | 18 | } 19 | 20 | protected override void LogStateInfoForContext() 21 | { 22 | CakeContext.Information($"Found '{Items.Count}' tool projects"); 23 | } 24 | } 25 | 26 | //------------------------------------------------------------- 27 | 28 | private ToolsContext InitializeToolsContext(BuildContext buildContext, IBuildContext parentBuildContext) 29 | { 30 | var data = new ToolsContext(parentBuildContext) 31 | { 32 | Items = Tools ?? new List(), 33 | NuGetRepositoryUrls = buildContext.BuildServer.GetVariable("ToolsNuGetRepositoryUrls", showValue: true), 34 | NuGetRepositoryApiKeys = buildContext.BuildServer.GetVariable("ToolsNuGetRepositoryApiKeys", showValue: false) 35 | }; 36 | 37 | return data; 38 | } 39 | 40 | //------------------------------------------------------------- 41 | 42 | List _tools; 43 | 44 | public List Tools 45 | { 46 | get 47 | { 48 | if (_tools is null) 49 | { 50 | _tools = new List(); 51 | } 52 | 53 | return _tools; 54 | } 55 | } -------------------------------------------------------------------------------- /deployment/cake/vsextensions-variables.cake: -------------------------------------------------------------------------------- 1 | #l "buildserver.cake" 2 | 3 | public class VsExtensionsContext : BuildContextWithItemsBase 4 | { 5 | public VsExtensionsContext(IBuildContext parentBuildContext) 6 | : base(parentBuildContext) 7 | { 8 | } 9 | 10 | public string PublisherName { get; set; } 11 | public string PersonalAccessToken { get; set; } 12 | 13 | protected override void ValidateContext() 14 | { 15 | 16 | } 17 | 18 | protected override void LogStateInfoForContext() 19 | { 20 | CakeContext.Information($"Found '{Items.Count}' vs extension projects"); 21 | } 22 | } 23 | 24 | //------------------------------------------------------------- 25 | 26 | private VsExtensionsContext InitializeVsExtensionsContext(BuildContext buildContext, IBuildContext parentBuildContext) 27 | { 28 | var data = new VsExtensionsContext(parentBuildContext) 29 | { 30 | Items = VsExtensions ?? new List(), 31 | PublisherName = buildContext.BuildServer.GetVariable("VsExtensionsPublisherName", showValue: true), 32 | PersonalAccessToken = buildContext.BuildServer.GetVariable("VsExtensionsPersonalAccessToken", showValue: false), 33 | }; 34 | 35 | return data; 36 | } 37 | 38 | //------------------------------------------------------------- 39 | 40 | List _vsExtensions; 41 | 42 | public List VsExtensions 43 | { 44 | get 45 | { 46 | if (_vsExtensions is null) 47 | { 48 | _vsExtensions = new List(); 49 | } 50 | 51 | return _vsExtensions; 52 | } 53 | } -------------------------------------------------------------------------------- /design/Package/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildGums/Orc.FileSystem/14c902160c6ccba8260d9541c43fb99880e48d33/design/Package/Icon.png -------------------------------------------------------------------------------- /src/.vsconfig: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "components": [ 4 | "Microsoft.VisualStudio.Component.CoreEditor", 5 | "Microsoft.VisualStudio.Workload.CoreEditor", 6 | "Microsoft.Net.Component.4.8.SDK", 7 | "Microsoft.Net.Component.4.7.2.TargetingPack", 8 | "Microsoft.Net.ComponentGroup.DevelopmentPrerequisites", 9 | "Microsoft.VisualStudio.Component.TypeScript.TSServer", 10 | "Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions", 11 | "Microsoft.VisualStudio.Component.JavaScript.TypeScript", 12 | "Microsoft.VisualStudio.Component.JavaScript.Diagnostics", 13 | "Microsoft.VisualStudio.Component.Roslyn.Compiler", 14 | "Microsoft.Component.MSBuild", 15 | "Microsoft.VisualStudio.Component.Roslyn.LanguageServices", 16 | "Microsoft.VisualStudio.Component.TextTemplating", 17 | "Component.Microsoft.VisualStudio.RazorExtension", 18 | "Microsoft.VisualStudio.Component.IISExpress", 19 | "Microsoft.VisualStudio.Component.NuGet", 20 | "Microsoft.VisualStudio.Component.MSODBC.SQL", 21 | "Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime", 22 | "Microsoft.VisualStudio.Component.Common.Azure.Tools", 23 | "Microsoft.VisualStudio.Component.SQL.CLR", 24 | "Microsoft.VisualStudio.Component.MSSQL.CMDLnUtils", 25 | "Microsoft.VisualStudio.Component.ManagedDesktop.Core", 26 | "Microsoft.VisualStudio.Component.SQL.SSDT", 27 | "Microsoft.VisualStudio.Component.SQL.DataSources", 28 | "Component.Microsoft.Web.LibraryManager", 29 | "Component.Microsoft.WebTools.BrowserLink.WebLivePreview", 30 | "Microsoft.VisualStudio.ComponentGroup.Web", 31 | "Microsoft.NetCore.Component.Runtime.8.0", 32 | "Microsoft.NetCore.Component.SDK", 33 | "Microsoft.VisualStudio.Component.FSharp", 34 | "Microsoft.NetCore.Component.DevelopmentTools", 35 | "Microsoft.VisualStudio.Component.FSharp.WebTemplates", 36 | "Microsoft.VisualStudio.Component.DockerTools", 37 | "Microsoft.NetCore.Component.Web", 38 | "Microsoft.VisualStudio.Component.WebDeploy", 39 | "Microsoft.VisualStudio.Component.AppInsights.Tools", 40 | "Microsoft.VisualStudio.Component.Web", 41 | "Microsoft.VisualStudio.Component.AspNet45", 42 | "Microsoft.VisualStudio.Component.AspNet", 43 | "Component.Microsoft.VisualStudio.Web.AzureFunctions", 44 | "Microsoft.VisualStudio.ComponentGroup.AzureFunctions", 45 | "Microsoft.VisualStudio.ComponentGroup.Web.CloudTools", 46 | "Microsoft.VisualStudio.Component.DiagnosticTools", 47 | "Microsoft.VisualStudio.Component.Debugger.JustInTime", 48 | "Microsoft.VisualStudio.Component.WslDebugging", 49 | "Microsoft.VisualStudio.Workload.NetWeb", 50 | "Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites", 51 | "Microsoft.VisualStudio.Workload.ManagedDesktop" 52 | ], 53 | "extensions": [] 54 | } -------------------------------------------------------------------------------- /src/Directory.Build.analyzers.props: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | all 9 | runtime; build; native; contentfiles; analyzers; buildtransitive 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 32 | latest 33 | 34 | 35 | 46 | 47 | $(NoWarn);CA1030;CA1031;CA1054;CA1062;CA1724;CA1810;CA2007;CA2237 48 | $(NoError);CA1030;CA1031;CA1054;CA1062;CA1724;CA810;CA2007;CA2237 49 | 50 | -------------------------------------------------------------------------------- /src/Directory.Build.implicitusings.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | 5 | -------------------------------------------------------------------------------- /src/Directory.Build.nullable.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | 5 | -------------------------------------------------------------------------------- /src/Directory.Build.project.props: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | wildgums 8 | https://github.com/WildGums/Orc.FileSystem 9 | MIT 10 | https://github.com/WildGums/Orc.FileSystem 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 11 | 13 | 14 | 15 | 16 | 18 | 20 | 22 | -------------------------------------------------------------------------------- /src/Directory.Build.shared.implicit.props: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | $(NoWarn);CA1416;CS1591;CS1998;NU1603;NU1605;NU1608;NU1701;AD0001;HAA0301;HAA0302;HAA0303;HAA0401;HAA0603 8 | $(NoError);CS1591;CS1998;NU1603;NU1605;NU1608;NU1701;AD0001;HAA0301;HAA0302;HAA0303;HAA0401;HAA0603 9 | true 10 | false 11 | true 12 | false 13 | false 14 | Release 15 | $(ProjectDir)..\..\output\$(Configuration)\ 16 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb;.xml 17 | False 18 | true 19 | 20 | 21 | 22 | 26 | 27 | 28 | true 29 | 30 | 31 | 32 | 33 | 34 | latest 35 | 36 | 37 | 38 | 40 | portable 41 | 42 | true 43 | 44 | 45 | 46 | 48 | portable 49 | true 50 | 51 | 52 | 53 | 55 | portable 56 | true 57 | 58 | 59 | 60 | 61 | 62 | direct 63 | 64 | low 65 | 66 | 67 | 68 | 69 | true 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/Directory.Build.shared.mat.props: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 4.0 10 | en-US 11 | true 12 | true 13 | true 14 | 15 | 16 | 17 | <_ResxFiles Remove="Properties\*.resx" /> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Directory.Build.shared.tests.props: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | false 10 | true 11 | true 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Directory.Build.shared.tools.props: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | $(Description) 10 | $(PackageProjectUrl) 11 | IncludeDefaultProjectBuildOutputInPack 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | $([MSBuild]::MakeRelative('$(OutputPath)\$(TargetFrameworks)\', %(ToolDllFiles.FullPath))) 31 | tools 32 | 33 | 34 | 35 | $([MSBuild]::MakeRelative('$(OutputPath)\$(TargetFrameworks)\', %(ToolExeFiles.FullPath))) 36 | tools 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Directory.Build.shared.xamltools.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | false 35 | 36 | 37 | 38 | true 39 | 40 | 41 | 42 | 43 | False 44 | False 45 | False 46 | 47 | 48 | 49 | 50 | True 51 | 52 | 53 | 54 | 55 | False 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 |  2 | // This file is used by Code Analysis to maintain SuppressMessage 3 | // attributes that are applied to this project. 4 | // Project-level suppressions either have no target or are given 5 | // a specific target and scoped to a namespace, type, member, etc. 6 | 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("WpfAnalyzers.DependencyProperties", "WPF1010:Property '[property]' must notify when value changes.", Justification = "Don't enforce this")] 8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("WpfAnalyzers.DependencyProperties", "WPF1011:Implement INotifyPropertyChanged.", Justification = "Don't enforce this")] 9 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("WpfAnalyzers.DependencyProperties", "WPF1013:Use [CallerMemberName].", Justification = "Don't enforce this, base class doesn't neccessarily support this")] 10 | 11 | -------------------------------------------------------------------------------- /src/MethodTimeLogger.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Catel.Logging; 3 | using System; 4 | using System.Globalization; 5 | 6 | /// 7 | /// Note: do not rename this class or put it inside a namespace. 8 | /// 9 | internal static class MethodTimeLogger 10 | { 11 | public static void Log(MethodBase methodBase, long milliseconds, string message) 12 | { 13 | Log(methodBase.DeclaringType ?? typeof(object), methodBase.Name, milliseconds, message); 14 | } 15 | 16 | public static void Log(Type type, string methodName, long milliseconds, string message) 17 | { 18 | if (type is null) 19 | { 20 | return; 21 | } 22 | 23 | if (milliseconds == 0) 24 | { 25 | // Don't log superfast methods 26 | return; 27 | } 28 | 29 | var finalMessage = $"[METHODTIMER] {type.Name}.{methodName} took '{milliseconds.ToString(CultureInfo.InvariantCulture)}' ms"; 30 | 31 | if (!string.IsNullOrWhiteSpace(message)) 32 | { 33 | finalMessage += $" | {message}"; 34 | } 35 | 36 | var logger = LogManager.GetLogger(type); 37 | logger.Debug(finalMessage); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem.Example; 2 | 3 | using System.Globalization; 4 | using System.Windows; 5 | using Catel.IoC; 6 | using Catel.Logging; 7 | using Catel.Services; 8 | using Orchestra; 9 | 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | public App() 16 | { 17 | #if DEBUG 18 | LogManager.AddDebugListener(false); 19 | #endif 20 | } 21 | 22 | protected override void OnStartup(StartupEventArgs e) 23 | { 24 | var languageService = ServiceLocator.Default.ResolveType(); 25 | 26 | // Note: it's best to use .CurrentUICulture in actual apps since it will use the preferred language 27 | // of the user. But in order to demo multilingual features for devs (who mostly have en-US as .CurrentUICulture), 28 | // we use .CurrentCulture for the sake of the demo 29 | languageService.PreferredCulture = CultureInfo.CurrentCulture; 30 | languageService.FallbackCulture = new CultureInfo("en-US"); 31 | 32 | base.OnStartup(e); 33 | 34 | this.ApplyTheme(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded. 3 | /// 4 | public static class ModuleInitializer 5 | { 6 | /// 7 | /// Initializes the module. 8 | /// 9 | public static void Initialize() 10 | { 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/Orc.FileSystem.Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0-windows 4 | Orc.FileSystem.Example 5 | Orc.FileSystem.Example 6 | en-US 7 | true 8 | 9 | 10 | 11 | true 12 | WinExe 13 | 14 | SA1652 15 | 16 | 17 | 18 | 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // All other assembly info is defined in SharedAssembly.cs 6 | 7 | [assembly: AssemblyTitle("Orc.FileSystem.Example")] 8 | [assembly: AssemblyProduct("Orc.FileSystem.Example")] 9 | [assembly: AssemblyDescription("Orc.FileSystem.Example")] 10 | 11 | // Setting ComVisible to false makes the types in this assembly not visible 12 | // to COM components. If you need to access a type in this assembly from 13 | // COM, set the ComVisible attribute to true on that type. 14 | 15 | [assembly: ComVisible(false)] 16 | 17 | [assembly: InternalsVisibleTo("Orc.FileSystem.Tests")] 18 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem.ViewModels; 2 | 3 | using Catel.MVVM; 4 | 5 | public class MainViewModel : ViewModelBase 6 | { 7 | public MainViewModel() 8 | { 9 | Title = "Orc.FileSystem example"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Example/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem.Views; 2 | 3 | public partial class MainWindow 4 | { 5 | public MainWindow() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Tests/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Tests/Helpers/IOSynchronizationWithoutSeparateSyncFileService.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem.Tests; 2 | 3 | public class IOSynchronizationWithoutSeparateSyncFileService : IOSynchronizationService 4 | { 5 | public IOSynchronizationWithoutSeparateSyncFileService(IFileService fileService, IDirectoryService directoryService) 6 | : base(fileService, directoryService) 7 | { 8 | } 9 | 10 | protected override string ResolveObservedFileName(string path) 11 | { 12 | return path; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Tests/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | using Catel.Logging; 2 | 3 | /// 4 | /// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded. 5 | /// 6 | public static class ModuleInitializer 7 | { 8 | /// 9 | /// Initializes the module. 10 | /// 11 | public static void Initialize() 12 | { 13 | LogManager.AddDebugListener(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Tests/Orc.FileSystem.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | Orc.FileSystem.Tests 5 | Orc.FileSystem.Tests 6 | en-US 7 | Orc.FileSystem.Tests 8 | 1.0.0-alpha0001 9 | 10 | 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // All other assembly info is defined in SharedAssembly.cs 5 | 6 | [assembly: AssemblyTitle("Orc.FileSystem.Tests")] 7 | [assembly: AssemblyProduct("Orc.FileSystem.Tests")] 8 | [assembly: AssemblyDescription("Orc.FileSystem.Tests library")] 9 | 10 | // Setting ComVisible to false makes the types in this assembly not visible 11 | // to COM components. If you need to access a type in this assembly from 12 | // COM, set the ComVisible attribute to true on that type. 13 | 14 | [assembly: ComVisible(false)] 15 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Tests/PublicApiFacts.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem.Tests; 2 | 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | using PublicApiGenerator; 8 | using VerifyNUnit; 9 | 10 | [TestFixture] 11 | public class PublicApiFacts 12 | { 13 | [Test, MethodImpl(MethodImplOptions.NoInlining)] 14 | public async Task Orc_FileSystem_HasNoBreakingChanges_Async() 15 | { 16 | var assembly = typeof(FileService).Assembly; 17 | 18 | await PublicApiApprover.ApprovePublicApiAsync(assembly); 19 | } 20 | 21 | internal static class PublicApiApprover 22 | { 23 | public static async Task ApprovePublicApiAsync(Assembly assembly) 24 | { 25 | var publicApi = ApiGenerator.GeneratePublicApi(assembly, new ApiGeneratorOptions()); 26 | await Verifier.Verify(publicApi); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.Tests/TemporaryFilesContext.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem.Tests; 2 | 3 | using System; 4 | using System.IO; 5 | using Catel.Logging; 6 | 7 | public sealed class TemporaryFilesContext : IDisposable 8 | { 9 | #region Constants 10 | private static readonly ILog Log = LogManager.GetCurrentClassLogger(); 11 | #endregion 12 | 13 | #region Fields 14 | private readonly Guid _randomGuid = Guid.NewGuid(); 15 | private readonly string _rootDirectory; 16 | #endregion 17 | 18 | #region Constructors 19 | public TemporaryFilesContext(string name = null) 20 | { 21 | if (string.IsNullOrWhiteSpace(name)) 22 | { 23 | name = _randomGuid.ToString(); 24 | } 25 | 26 | _rootDirectory = Path.Combine(Path.GetTempPath(), GetType().Assembly.GetName().Name, name); 27 | 28 | Directory.CreateDirectory(_rootDirectory); 29 | } 30 | #endregion 31 | 32 | #region IDisposable Members 33 | public void Dispose() 34 | { 35 | Log.Debug("Deleting temporary files from '{0}'", _rootDirectory); 36 | 37 | try 38 | { 39 | if (Directory.Exists(_rootDirectory)) 40 | { 41 | Directory.Delete(_rootDirectory, true); 42 | } 43 | } 44 | catch (Exception ex) 45 | { 46 | Log.Warning(ex, "Failed to delete temporary files"); 47 | } 48 | } 49 | #endregion 50 | 51 | #region Methods 52 | public string GetDirectory(string relativeDirectoryName) 53 | { 54 | var fullPath = Path.Combine(_rootDirectory, relativeDirectoryName); 55 | 56 | if (!Directory.Exists(fullPath)) 57 | { 58 | Directory.CreateDirectory(fullPath); 59 | } 60 | 61 | return fullPath; 62 | } 63 | 64 | public string GetFile(string relativeFilePath, bool deleteIfExists = false) 65 | { 66 | var fullPath = Path.Combine(_rootDirectory, relativeFilePath); 67 | 68 | var directory = Path.GetDirectoryName(fullPath); 69 | if (!Directory.Exists(directory)) 70 | { 71 | Directory.CreateDirectory(directory); 72 | } 73 | 74 | if (deleteIfExists) 75 | { 76 | if (File.Exists(fullPath)) 77 | { 78 | File.Delete(fullPath); 79 | } 80 | } 81 | 82 | return fullPath; 83 | } 84 | #endregion 85 | } 86 | -------------------------------------------------------------------------------- /src/Orc.FileSystem.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27130.2036 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A8A1A4EE-E04A-43AB-90C9-41E7B82EE1E5}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{1FE0D431-A761-4239-B2D6-D32C70723FD2}" 8 | ProjectSection(SolutionItems) = preProject 9 | Directory.build.project.props = Directory.build.project.props 10 | Directory.build.props = Directory.build.props 11 | Directory.build.shared.explicit.props = Directory.build.shared.explicit.props 12 | Directory.build.shared.implicit.props = Directory.build.shared.implicit.props 13 | Directory.build.shared.mat.props = Directory.build.shared.mat.props 14 | Directory.build.targets = Directory.build.targets 15 | GlobalSuppressions.cs = GlobalSuppressions.cs 16 | nuget.config = nuget.config 17 | Orc.FileSystem.sln.DotSettings = Orc.FileSystem.sln.DotSettings 18 | SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs 19 | EndProjectSection 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{0787DFC6-AB24-4D00-A8A4-FF203DC7D080}" 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D3EA1078-BF3D-4709-871C-AAFF70B95045}" 24 | EndProject 25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Example", "Example", "{672BFA2E-9F93-496F-B188-40579FB5D301}" 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orc.FileSystem", "Orc.FileSystem\Orc.FileSystem.csproj", "{6AFB2ED7-711A-4E64-AD45-7C37ED6F8879}" 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orc.FileSystem.Example", "Orc.FileSystem.Example\Orc.FileSystem.Example.csproj", "{043839D7-98CA-4E16-B7D6-0647C68C7CFB}" 30 | EndProject 31 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orc.FileSystem.Tests", "Orc.FileSystem.Tests\Orc.FileSystem.Tests.csproj", "{8B2140EB-431C-497F-AE36-02DE53C9641E}" 32 | EndProject 33 | Global 34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 35 | Debug|Any CPU = Debug|Any CPU 36 | Release|Any CPU = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 39 | {6AFB2ED7-711A-4E64-AD45-7C37ED6F8879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {6AFB2ED7-711A-4E64-AD45-7C37ED6F8879}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {6AFB2ED7-711A-4E64-AD45-7C37ED6F8879}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {6AFB2ED7-711A-4E64-AD45-7C37ED6F8879}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {043839D7-98CA-4E16-B7D6-0647C68C7CFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {043839D7-98CA-4E16-B7D6-0647C68C7CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {043839D7-98CA-4E16-B7D6-0647C68C7CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {043839D7-98CA-4E16-B7D6-0647C68C7CFB}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {8B2140EB-431C-497F-AE36-02DE53C9641E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {8B2140EB-431C-497F-AE36-02DE53C9641E}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {8B2140EB-431C-497F-AE36-02DE53C9641E}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {8B2140EB-431C-497F-AE36-02DE53C9641E}.Release|Any CPU.Build.0 = Release|Any CPU 51 | EndGlobalSection 52 | GlobalSection(SolutionProperties) = preSolution 53 | HideSolutionNode = FALSE 54 | EndGlobalSection 55 | GlobalSection(NestedProjects) = preSolution 56 | {0787DFC6-AB24-4D00-A8A4-FF203DC7D080} = {A8A1A4EE-E04A-43AB-90C9-41E7B82EE1E5} 57 | {D3EA1078-BF3D-4709-871C-AAFF70B95045} = {A8A1A4EE-E04A-43AB-90C9-41E7B82EE1E5} 58 | {672BFA2E-9F93-496F-B188-40579FB5D301} = {A8A1A4EE-E04A-43AB-90C9-41E7B82EE1E5} 59 | {6AFB2ED7-711A-4E64-AD45-7C37ED6F8879} = {0787DFC6-AB24-4D00-A8A4-FF203DC7D080} 60 | {043839D7-98CA-4E16-B7D6-0647C68C7CFB} = {672BFA2E-9F93-496F-B188-40579FB5D301} 61 | {8B2140EB-431C-497F-AE36-02DE53C9641E} = {D3EA1078-BF3D-4709-871C-AAFF70B95045} 62 | EndGlobalSection 63 | GlobalSection(ExtensibilityGlobals) = postSolution 64 | SolutionGuid = {AF55400B-59C5-41B1-8651-8218BCDC7EA0} 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | // for full list of error codes see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx 4 | public static class SystemErrorCodes 5 | { 6 | /// 7 | /// The process cannot access the file because it is being used by another process. 8 | /// 9 | public const uint ERROR_SHARING_VIOLATION = 0x80070020; 10 | } -------------------------------------------------------------------------------- /src/Orc.FileSystem/Exceptions/FileLockScopeException.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | 5 | [Serializable] 6 | public class FileLockScopeException : Exception 7 | { 8 | public FileLockScopeException() 9 | { 10 | 11 | } 12 | 13 | public FileLockScopeException(string message) 14 | : base(message) 15 | { 16 | } 17 | 18 | public FileLockScopeException(string message, Exception innerException) 19 | : base(message, innerException) 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Exceptions/IOSynchronizationException.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | 5 | [Serializable] 6 | public class IOSynchronizationException : Exception 7 | { 8 | public IOSynchronizationException() 9 | { 10 | 11 | } 12 | 13 | public IOSynchronizationException(string message) 14 | : base(message) 15 | { 16 | } 17 | 18 | public IOSynchronizationException(string message, Exception innerException) 19 | : base(message, innerException) 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Extensions/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | 5 | public static class ExceptionExtensions 6 | { 7 | public static int GetHResult(this Exception exception) 8 | { 9 | return exception.HResult; 10 | } 11 | } -------------------------------------------------------------------------------- /src/Orc.FileSystem/Extensions/FileInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | using System.Threading; 8 | 9 | public static class FileInfoExtensions 10 | { 11 | private const int TimerTickIntervalInMilliseconds = 50; 12 | 13 | public static async Task EnsureFilesNotBusyAsync(this IEnumerable files) 14 | { 15 | var tcs = new TaskCompletionSource(); 16 | 17 | Timer? timer = null; 18 | 19 | var handler = new TimerCallback(x => 20 | { 21 | foreach (var file in files) 22 | { 23 | try 24 | { 25 | if (!file.Exists) 26 | { 27 | continue; 28 | } 29 | 30 | using (file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 31 | { 32 | // don't do anything 33 | } 34 | } 35 | catch (IOException) 36 | { 37 | timer?.Change(TimerTickIntervalInMilliseconds, Timeout.Infinite); 38 | } 39 | catch (Exception ex) 40 | { 41 | tcs.TrySetException(ex); 42 | return; 43 | } 44 | } 45 | 46 | tcs.TrySetResult(null); 47 | }); 48 | 49 | using (timer = new Timer(handler, null, TimerTickIntervalInMilliseconds, Timeout.Infinite)) 50 | { 51 | await tcs.Task; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Orc.FileSystem/Extensions/ILogExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System.Diagnostics; 4 | using Catel.Logging; 5 | 6 | internal static class ILogExtensions 7 | { 8 | private static readonly bool IsDebuggerAttached; 9 | 10 | static ILogExtensions() 11 | { 12 | IsDebuggerAttached = Debugger.IsAttached; 13 | } 14 | 15 | public static void DebugIfAttached(this ILog log, string message) 16 | { 17 | if (IsDebuggerAttached) 18 | { 19 | log.Debug(message); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Extensions/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | 7 | public static class StreamExtensions 8 | { 9 | public static byte[] ReadAllBytes(this Stream stream) 10 | { 11 | ArgumentNullException.ThrowIfNull(stream); 12 | 13 | const int bufferSize = 2048; 14 | 15 | var bytes = new byte[stream.Length]; 16 | 17 | var buffer = new byte[bufferSize]; 18 | var totalBytesRead = 0; 19 | int bytesRead; 20 | 21 | do 22 | { 23 | bytesRead = stream.Read(buffer, 0, bufferSize); 24 | 25 | Buffer.BlockCopy(buffer, 0, bytes, totalBytesRead, bytesRead); 26 | 27 | totalBytesRead += bytesRead; 28 | 29 | } while (bytesRead > 0); 30 | 31 | return bytes; 32 | } 33 | 34 | public static async Task ReadAllBytesAsync(this Stream stream) 35 | { 36 | ArgumentNullException.ThrowIfNull(stream); 37 | 38 | const int bufferSize = 2048; 39 | 40 | var bytes = new byte[stream.Length]; 41 | 42 | var buffer = new byte[bufferSize]; 43 | var totalBytesRead = 0; 44 | var bytesRead = 0; 45 | 46 | do 47 | { 48 | bytesRead = await stream.ReadAsync(buffer, 0, bufferSize); 49 | 50 | Buffer.BlockCopy(buffer, 0, bytes, totalBytesRead, bytesRead); 51 | 52 | totalBytesRead += bytesRead; 53 | 54 | } while (bytesRead > 0); 55 | 56 | return bytes; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Locking/FileLockScope.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using Catel; 7 | using Catel.Logging; 8 | 9 | public class FileLockScope : Disposable 10 | { 11 | private static readonly ILog Log = LogManager.GetCurrentClassLogger(); 12 | 13 | private static readonly FileLockScope _dummyLock = new(); 14 | 15 | private readonly IFileService? _fileService; 16 | private readonly bool _isReadScope; 17 | 18 | private readonly object _lock = new object(); 19 | private readonly string? _syncFile; 20 | 21 | #pragma warning disable IDISP006 // Implement IDisposable. 22 | private Stream? _stream; 23 | #pragma warning restore IDISP006 // Implement IDisposable. 24 | 25 | private int _lockAttemptCounter; 26 | 27 | private FileLockScope() 28 | { 29 | // DummyLock 30 | } 31 | 32 | public FileLockScope(bool isReadScope, string syncFile, IFileService fileService) 33 | { 34 | Argument.IsNotNullOrWhitespace(() => syncFile); 35 | ArgumentNullException.ThrowIfNull(fileService); 36 | 37 | _isReadScope = isReadScope; 38 | _syncFile = syncFile; 39 | _fileService = fileService; 40 | } 41 | 42 | private bool HasStream 43 | { 44 | get 45 | { 46 | lock (_lock) 47 | { 48 | return _stream is not null; 49 | } 50 | } 51 | } 52 | 53 | private bool IsDummyLock => string.IsNullOrWhiteSpace(_syncFile); 54 | 55 | public static FileLockScope DummyLock 56 | { 57 | get 58 | { 59 | return _dummyLock; 60 | } 61 | } 62 | 63 | public bool NotifyOnRelease { get; set; } 64 | 65 | public void WriteDummyContent() 66 | { 67 | if (IsDummyLock) 68 | { 69 | return; 70 | } 71 | 72 | // Note: writing dummy data for FileSystemWatcher 73 | lock (_lock) 74 | { 75 | _stream?.WriteByte(0); 76 | } 77 | } 78 | 79 | public bool Lock() 80 | { 81 | lock (_lock) 82 | { 83 | var syncFile = _syncFile; 84 | if (syncFile is null || IsDummyLock || HasStream) 85 | { 86 | return true; 87 | } 88 | 89 | try 90 | { 91 | // Note: don't use _fileService because we don't want logging in case of failure 92 | _stream?.Dispose(); 93 | _stream = File.Open(syncFile, FileMode.Create, FileAccess.Write, _isReadScope ? FileShare.Delete : FileShare.None); 94 | 95 | Log.Debug($"Locked synchronization file '{syncFile}'"); 96 | } 97 | catch (IOException ex) 98 | { 99 | var hResult = (uint)ex.GetHResult(); 100 | if (hResult != SystemErrorCodes.ERROR_SHARING_VIOLATION) 101 | { 102 | Log.Warning(ex, $"Failed to lock synchronization file '{syncFile}'"); 103 | 104 | throw Log.ErrorAndCreateException(message => new FileLockScopeException(message, ex), $"Failed to lock synchronization file '{syncFile}'"); 105 | } 106 | 107 | if (_lockAttemptCounter > 0) 108 | { 109 | return false; 110 | } 111 | 112 | var processes = FileLockInfo.GetProcessesLockingFile(syncFile); 113 | if (processes is null || !processes.Any()) 114 | { 115 | Log.Debug(ex, $"First attempt to lock synchronization file '{syncFile}' was unsuccessful. " + 116 | "Possibly locked by unknown application. Will keep retrying in the background."); 117 | } 118 | else 119 | { 120 | Log.Debug($"First attempt to lock synchronization file '{syncFile}' was unsuccessful. " + 121 | $"Locked by: {string.Join(", ", processes)}. Will keep retrying in the background."); 122 | } 123 | 124 | return false; 125 | } 126 | finally 127 | { 128 | _lockAttemptCounter++; 129 | } 130 | 131 | return true; 132 | } 133 | } 134 | 135 | public void Unlock() 136 | { 137 | if (IsDummyLock) 138 | { 139 | return; 140 | } 141 | 142 | if (NotifyOnRelease) 143 | { 144 | WriteDummyContent(); 145 | } 146 | 147 | if (_isReadScope) 148 | { 149 | // Note: deleting sync file before releasing, in order to prevent locking by another application 150 | DeleteSyncFile(); 151 | } 152 | 153 | if (_stream is not null) 154 | { 155 | _stream.Dispose(); 156 | _stream = null; 157 | 158 | Log.Debug($"Unlocked synchronization file '{_syncFile}'"); 159 | } 160 | 161 | _lockAttemptCounter = 0; 162 | } 163 | 164 | protected override void DisposeManaged() 165 | { 166 | Unlock(); 167 | } 168 | 169 | private void DeleteSyncFile() 170 | { 171 | var syncFile = _syncFile; 172 | if (syncFile is null) 173 | { 174 | return; 175 | } 176 | 177 | try 178 | { 179 | var fileService = _fileService; 180 | if (fileService is null) 181 | { 182 | return; 183 | } 184 | 185 | if (fileService.Exists(syncFile)) 186 | { 187 | fileService.Delete(syncFile); 188 | 189 | Log.Debug($"Deleted synchronization file '{syncFile}'"); 190 | } 191 | } 192 | catch (IOException ex) 193 | { 194 | var processes = FileLockInfo.GetProcessesLockingFile(syncFile); 195 | if (processes is null || !processes.Any()) 196 | { 197 | Log.Warning(ex, $"Failed to delete synchronization file '{syncFile}'"); 198 | } 199 | else 200 | { 201 | Log.Warning(ex, $"Failed to delete synchronization file '{syncFile}' locked by: {string.Join(", ", processes)}"); 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Locking/FileLocker.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Catel; 10 | using Catel.Logging; 11 | using Catel.Threading; 12 | 13 | public sealed class FileLocker : IDisposable 14 | { 15 | private static readonly ILog Log = LogManager.GetCurrentClassLogger(); 16 | 17 | private static readonly Dictionary Locks = new(StringComparer.OrdinalIgnoreCase); 18 | private static readonly Dictionary LockCounts = new(StringComparer.OrdinalIgnoreCase); 19 | private static readonly AsyncLock AsyncLock = new(); 20 | 21 | private readonly FileLocker? _existingLocker; 22 | private readonly int _uniqueId = UniqueIdentifierHelper.GetUniqueIdentifier(); 23 | 24 | private readonly HashSet _internalLocks = new(StringComparer.OrdinalIgnoreCase); 25 | 26 | private bool _isDisposed; 27 | 28 | public FileLocker(FileLocker? existingLocker = null) 29 | { 30 | _existingLocker = existingLocker; 31 | } 32 | 33 | public void Dispose() 34 | { 35 | if (_isDisposed) 36 | { 37 | return; 38 | } 39 | 40 | ReleaseLockedFiles(); 41 | 42 | _isDisposed = true; 43 | } 44 | 45 | public Task LockFilesAsync(params string[] files) 46 | { 47 | return LockFilesAsync(TimeSpan.FromSeconds(5), files); 48 | } 49 | 50 | public async Task LockFilesAsync(TimeSpan timeout, params string[] files) 51 | { 52 | if (_existingLocker is not null) 53 | { 54 | await _existingLocker.LockFilesAsync(timeout, files); 55 | return; 56 | } 57 | 58 | using (await AsyncLock.LockAsync()) 59 | { 60 | var newLockFiles = files.Where(x => !x.EndsWith(".lock", StringComparison.OrdinalIgnoreCase)).Select(x => x + ".lock"); 61 | string[] fileNames; 62 | 63 | lock (Locks) 64 | { 65 | // Note: instead of adding new locked files better to release already locked ones and lock them again combined with the new ones 66 | // I think it should prevent hangings in concurrent applications 67 | fileNames = newLockFiles.Union(_internalLocks, StringComparer.OrdinalIgnoreCase).ToArray(); 68 | ReleaseLockedFiles(); 69 | } 70 | 71 | Log.Debug($"[{_uniqueId}] Creating and locking following files"); 72 | foreach (var file in fileNames) 73 | { 74 | Log.Debug($"[{_uniqueId}] * {file}"); 75 | } 76 | 77 | var continueLoop = true; 78 | 79 | var timerHandler = new TimerCallback(x => 80 | { 81 | continueLoop = false; 82 | 83 | Log.Warning("Locking files has interrupted due to timeout"); 84 | }); 85 | 86 | await using (var timer = new Timer(timerHandler, null, timeout, Timeout.InfiniteTimeSpan)) 87 | { 88 | while (continueLoop) 89 | { 90 | var lockedFiles = TryCreateAndLockFiles(fileNames); 91 | switch (lockedFiles.Any()) 92 | { 93 | case false when continueLoop: 94 | await Task.Delay(10); 95 | continue; 96 | 97 | case false: 98 | continue; 99 | } 100 | 101 | lock (Locks) 102 | { 103 | foreach (var fileName in fileNames) 104 | { 105 | _internalLocks.Add(fileName); 106 | 107 | LockCounts.TryGetValue(fileName, out var count); 108 | count++; 109 | LockCounts[fileName] = count; 110 | 111 | if (lockedFiles.TryGetValue(fileName, out var stream)) 112 | { 113 | Locks[fileName] = stream; 114 | } 115 | } 116 | 117 | continueLoop = false; 118 | } 119 | } 120 | } 121 | } 122 | } 123 | 124 | private static Dictionary TryCreateAndLockFiles(string[] fileNames) 125 | { 126 | var result = new Dictionary(); 127 | 128 | foreach (var fileName in fileNames) 129 | { 130 | try 131 | { 132 | result[fileName] = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); 133 | File.SetAttributes(fileName, FileAttributes.Hidden); 134 | } 135 | catch (IOException) 136 | { 137 | foreach (var stream in result.Values.Where(x => x is not null)) 138 | { 139 | stream.Dispose(); 140 | } 141 | 142 | result.Clear(); 143 | 144 | break; 145 | } 146 | catch (Exception) 147 | { 148 | foreach (var stream in result.Values.Where(x => x is not null)) 149 | { 150 | stream.Dispose(); 151 | } 152 | 153 | result.Clear(); 154 | 155 | throw; 156 | } 157 | } 158 | 159 | return result; 160 | } 161 | 162 | private void ReleaseLockedFiles() 163 | { 164 | lock (Locks) 165 | { 166 | Log.Debug("Releasing locked files"); 167 | 168 | foreach (var lockFile in _internalLocks.ToList()) 169 | { 170 | LockCounts.TryGetValue(lockFile, out var count); 171 | 172 | _internalLocks.Remove(lockFile); 173 | 174 | if (count > 0) 175 | { 176 | count--; 177 | } 178 | 179 | if (count <= 0 && Locks.TryGetValue(lockFile, out var lockStream)) 180 | { 181 | lockStream.Dispose(); 182 | 183 | Locks.Remove(lockFile); 184 | 185 | Log.Debug($"'{lockFile}' released"); 186 | } 187 | 188 | if (count <= 0 && File.Exists(lockFile)) 189 | { 190 | try 191 | { 192 | File.Delete(lockFile); 193 | 194 | Log.Debug($"'{lockFile}' deleted"); 195 | } 196 | catch (Exception ex) 197 | { 198 | // it is not a reason for crashing the app 199 | Log.Warning(ex, $"Failed to delete '{lockFile}'"); 200 | } 201 | } 202 | 203 | if (count > 0) 204 | { 205 | LockCounts[lockFile] = count; 206 | } 207 | else 208 | { 209 | LockCounts.Remove(lockFile); 210 | } 211 | } 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | using Catel.IoC; 2 | using Catel.Services; 3 | using Orc.FileSystem; 4 | 5 | /// 6 | /// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded. 7 | /// 8 | public static class ModuleInitializer 9 | { 10 | /// 11 | /// Initializes the module. 12 | /// 13 | public static void Initialize() 14 | { 15 | var serviceLocator = ServiceLocator.Default; 16 | 17 | serviceLocator.RegisterType(); 18 | serviceLocator.RegisterType(); 19 | serviceLocator.RegisterType(); 20 | 21 | var languageService = serviceLocator.ResolveRequiredType(); 22 | languageService.RegisterLanguageSource(new LanguageResourceSource("Orc.FileSystem", "Orc.FileSystem.Properties", "Resources")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/MultilingualResources/Orc.FileSystem.de.xlf: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 |
7 | 8 | 9 | 10 |
11 |
-------------------------------------------------------------------------------- /src/Orc.FileSystem/MultilingualResources/Orc.FileSystem.es.xlf: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 |
7 | 8 | 9 | 10 |
11 |
-------------------------------------------------------------------------------- /src/Orc.FileSystem/MultilingualResources/Orc.FileSystem.fr.xlf: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 |
7 | 8 | 9 | 10 |
11 |
-------------------------------------------------------------------------------- /src/Orc.FileSystem/MultilingualResources/Orc.FileSystem.nl.xlf: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 |
7 | 8 | 9 | 10 |
11 |
-------------------------------------------------------------------------------- /src/Orc.FileSystem/MultilingualResources/Orc.FileSystem.ru.xlf: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 |
7 | 8 | 9 | 10 |
11 |
-------------------------------------------------------------------------------- /src/Orc.FileSystem/Orc.FileSystem.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0;net9.0 4 | Orc.FileSystem 5 | Orc.FileSystem 6 | en-US 7 | Orc.FileSystem 8 | 1.0.0-alpha0001 9 | File system access using services and automatic logging. 10 | orc;file;directory 11 | 12 | 13 | 14 | 15 | 360C62C6-7905-46D0-8B93-ABD90857E478 16 | 17 | 18 | 19 | 20 | 21 | 22 | runtime; build; native; contentfiles; analyzers 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | 5 | // All other assembly info is defined in SolutionAssemblyInfo.cs 6 | 7 | [assembly: AssemblyTitle("Orc.FileSystem")] 8 | [assembly: AssemblyProduct("Orc.FileSystem")] 9 | [assembly: AssemblyDescription("Orc.FileSystem library")] 10 | [assembly: NeutralResourcesLanguage("en-US")] 11 | 12 | [assembly: InternalsVisibleTo("Orc.FileSystem.Tests")] 13 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Orc.FileSystem.Properties { 12 | using System; 13 | using Catel.Reflection; 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Orc.FileSystem.Properties.Resources", typeof(Resources).GetAssemblyEx()); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Properties/Resources.de.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | text/microsoft-resx 5 | 6 | 7 | 2.0 8 | 9 | 10 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 11 | 12 | 13 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 14 | 15 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Properties/Resources.es.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | text/microsoft-resx 5 | 6 | 7 | 2.0 8 | 9 | 10 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 11 | 12 | 13 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 14 | 15 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Properties/Resources.fr.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | text/microsoft-resx 5 | 6 | 7 | 2.0 8 | 9 | 10 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 11 | 12 | 13 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 14 | 15 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Properties/Resources.nl.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | text/microsoft-resx 5 | 6 | 7 | 2.0 8 | 9 | 10 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 11 | 12 | 13 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 14 | 15 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | text/microsoft-resx 91 | 92 | 93 | 1.3 94 | 95 | 96 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 97 | 98 | 99 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 100 | 101 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Properties/Resources.ru.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | text/microsoft-resx 5 | 6 | 7 | 2.0 8 | 9 | 10 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 11 | 12 | 13 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 14 | 15 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/DirectoryService.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.IO; 5 | using Catel; 6 | using Catel.Logging; 7 | 8 | public class DirectoryService : IDirectoryService 9 | { 10 | private static readonly ILog Log = LogManager.GetCurrentClassLogger(); 11 | 12 | private readonly IFileService _fileService; 13 | 14 | public DirectoryService(IFileService fileService) 15 | { 16 | ArgumentNullException.ThrowIfNull(fileService); 17 | 18 | _fileService = fileService; 19 | } 20 | 21 | public virtual string Create(string path) 22 | { 23 | Argument.IsNotNullOrWhitespace(() => path); 24 | 25 | try 26 | { 27 | if (Directory.Exists(path)) 28 | { 29 | return path; 30 | } 31 | 32 | Log.Debug($"Creating directory '{path}'"); 33 | 34 | var info = Directory.CreateDirectory(path); 35 | path = info.FullName; 36 | 37 | return path; 38 | } 39 | catch (Exception ex) 40 | { 41 | Log.Warning(ex, $"Failed to create directory '{path}'"); 42 | 43 | throw; 44 | } 45 | } 46 | 47 | public virtual void Move(string sourcePath, string destinationPath) 48 | { 49 | Argument.IsNotNullOrWhitespace(() => sourcePath); 50 | Argument.IsNotNullOrWhitespace(() => destinationPath); 51 | 52 | try 53 | { 54 | Log.Debug($"Moving directory '{sourcePath}' => '{destinationPath}'"); 55 | 56 | Directory.Move(sourcePath, destinationPath); 57 | } 58 | catch (Exception ex) 59 | { 60 | Log.Warning(ex, $"Failed to move directory '{sourcePath}' => '{destinationPath}'"); 61 | 62 | throw; 63 | } 64 | } 65 | 66 | public virtual void Copy(string sourcePath, string destinationPath, bool copySubDirs = true, bool overwriteExisting = false) 67 | { 68 | Argument.IsNotNullOrWhitespace(() => sourcePath); 69 | Argument.IsNotNullOrWhitespace(() => destinationPath); 70 | 71 | if (!Exists(sourcePath)) 72 | { 73 | Log.Warning($"Source directory '{sourcePath}' does not exist or could not be found"); 74 | 75 | throw Log.ErrorAndCreateException($"Source directory '{sourcePath}' does not exist or could not be found"); 76 | } 77 | 78 | Log.Debug($"Copying directory '{sourcePath}' to '{destinationPath}'"); 79 | 80 | Create(destinationPath); 81 | 82 | var files = GetFiles(sourcePath); 83 | foreach (var file in files) 84 | { 85 | var fileName = Path.GetFileName(file); 86 | var destinationFileName = Path.Combine(destinationPath, fileName); 87 | 88 | if (File.Exists(destinationFileName) && !overwriteExisting) 89 | { 90 | Log.Debug($"Skipping copying of '{file}', file already exists in target directory"); 91 | continue; 92 | } 93 | 94 | _fileService.Copy(file, destinationFileName, overwriteExisting); 95 | } 96 | 97 | if (!copySubDirs) 98 | { 99 | return; 100 | } 101 | 102 | var subDirectories = GetDirectories(sourcePath); 103 | 104 | foreach (var subDirectory in subDirectories) 105 | { 106 | var subDirectoryName = Path.GetDirectoryName(subDirectory); 107 | var destinationSubDirectory = Path.Combine(destinationPath, subDirectoryName ?? string.Empty); 108 | 109 | Copy(subDirectory, destinationSubDirectory, copySubDirs, overwriteExisting); 110 | } 111 | } 112 | 113 | public virtual void Delete(string path, bool recursive = true) 114 | { 115 | Argument.IsNotNullOrWhitespace(() => path); 116 | 117 | try 118 | { 119 | if (!Directory.Exists(path)) 120 | { 121 | return; 122 | } 123 | 124 | Log.Debug($"Deleting directory '{path}', recursive: '{recursive}'"); 125 | 126 | Directory.Delete(path, recursive); 127 | } 128 | catch (Exception ex) 129 | { 130 | Log.Warning(ex, $"Failed to delete directory '{path}'"); 131 | 132 | throw; 133 | } 134 | } 135 | 136 | public virtual bool Exists(string path) 137 | { 138 | Argument.IsNotNullOrWhitespace(() => path); 139 | 140 | try 141 | { 142 | var exists = Directory.Exists(path); 143 | return exists; 144 | } 145 | catch (Exception ex) 146 | { 147 | Log.Warning(ex, $"Failed to check whether directory '{path}' exists"); 148 | 149 | throw; 150 | } 151 | } 152 | 153 | public virtual string[] GetDirectories(string path, string searchPattern = "", SearchOption searchOption = SearchOption.TopDirectoryOnly) 154 | { 155 | Argument.IsNotNullOrWhitespace(() => path); 156 | 157 | try 158 | { 159 | if (string.IsNullOrWhiteSpace(searchPattern)) 160 | { 161 | searchPattern = "*"; 162 | } 163 | 164 | Log.Debug($"Getting directories inside '{path}', searchPattern: '{searchPattern}', searchOption: '{searchOption}'"); 165 | 166 | var directories = Directory.GetDirectories(path, searchPattern, searchOption); 167 | return directories; 168 | } 169 | catch (Exception ex) 170 | { 171 | Log.Warning(ex, $"Failed getting directories inside '{path}'"); 172 | 173 | throw; 174 | } 175 | } 176 | 177 | public virtual string[] GetFiles(string path, string searchPattern = "", SearchOption searchOption = SearchOption.TopDirectoryOnly) 178 | { 179 | Argument.IsNotNullOrWhitespace(() => path); 180 | 181 | try 182 | { 183 | if (string.IsNullOrWhiteSpace(searchPattern)) 184 | { 185 | searchPattern = "*"; 186 | } 187 | 188 | Log.Debug($"Getting files inside '{path}', searchPattern: '{searchPattern}', searchOption: '{searchOption}'"); 189 | 190 | var files = Directory.GetFiles(path, searchPattern, searchOption); 191 | return files; 192 | } 193 | catch (Exception ex) 194 | { 195 | Log.Warning(ex, $"Failed getting files inside '{path}'"); 196 | 197 | throw; 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/EventArgs/PathEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using Catel; 5 | 6 | public class PathEventArgs : EventArgs 7 | { 8 | public PathEventArgs(string path) 9 | { 10 | Argument.IsNotNullOrWhitespace(() => path); 11 | 12 | Path = path; 13 | } 14 | 15 | public string Path { get; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/Extensions/IDirectoryServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using Catel.Logging; 7 | 8 | public static class IDirectoryServiceExtensions 9 | { 10 | private static readonly ILog Log = LogManager.GetCurrentClassLogger(); 11 | 12 | public static bool IsEmpty(this IDirectoryService directoryService, string path) 13 | { 14 | ArgumentNullException.ThrowIfNull(directoryService); 15 | ArgumentNullException.ThrowIfNull(path); 16 | 17 | if (!directoryService.Exists(path)) 18 | { 19 | // If it doesn't exist, it's empty 20 | return true; 21 | } 22 | 23 | if (directoryService.GetFiles(path).Any()) 24 | { 25 | return false; 26 | } 27 | 28 | // We are assuming that, even if we have subdirectories, they could all be empty (e.g. we are checking for 29 | // an empty directory tree) 30 | foreach (var subDirectory in directoryService.GetDirectories(path)) 31 | { 32 | if (!IsEmpty(directoryService, subDirectory)) 33 | { 34 | return false; 35 | } 36 | } 37 | 38 | return true; 39 | } 40 | 41 | public static ulong GetSize(this IDirectoryService directoryService, string path) 42 | { 43 | ArgumentNullException.ThrowIfNull(directoryService); 44 | ArgumentNullException.ThrowIfNull(path); 45 | 46 | ulong size = 0L; 47 | 48 | try 49 | { 50 | if (directoryService.Exists(path)) 51 | { 52 | size += (ulong)(from fileName in directoryService.GetFiles(path, "*", SearchOption.AllDirectories) 53 | select new FileInfo(fileName)).Sum(x => x.Length); 54 | } 55 | } 56 | catch (Exception ex) 57 | { 58 | Log.Warning(ex, "Failed to calculate the size of directory '{0}'", path); 59 | } 60 | 61 | return size; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/Extensions/IFileServiceExtensions.bytes.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | using Catel; 7 | using Catel.Logging; 8 | 9 | public static partial class IFileServiceExtensions 10 | { 11 | public static byte[] ReadAllBytes(this IFileService fileService, string fileName) 12 | { 13 | ArgumentNullException.ThrowIfNull(fileService); 14 | Argument.IsNotNullOrWhitespace(() => fileName); 15 | 16 | try 17 | { 18 | using var stream = fileService.OpenRead(fileName); 19 | Log.Debug($"Reading all bytes from '{fileName}'"); 20 | 21 | var bytes = stream.ReadAllBytes(); 22 | return bytes; 23 | } 24 | catch (Exception ex) 25 | { 26 | Log.Warning(ex, $"Failed to read all bytes from '{fileName}'"); 27 | 28 | throw; 29 | } 30 | } 31 | 32 | public static async Task ReadAllBytesAsync(this IFileService fileService, string fileName) 33 | { 34 | ArgumentNullException.ThrowIfNull(fileService); 35 | Argument.IsNotNullOrWhitespace(() => fileName); 36 | 37 | try 38 | { 39 | await using var stream = fileService.OpenRead(fileName); 40 | Log.Debug($"Reading all bytes from '{fileName}'"); 41 | 42 | var bytes = await stream.ReadAllBytesAsync(); 43 | return bytes; 44 | } 45 | catch (Exception ex) 46 | { 47 | Log.Warning(ex, $"Failed to read all bytes from '{fileName}'"); 48 | 49 | throw; 50 | } 51 | } 52 | 53 | public static void WriteAllBytes(this IFileService fileService, string fileName, byte[] bytes) 54 | { 55 | ArgumentNullException.ThrowIfNull(fileService); 56 | Argument.IsNotNullOrWhitespace(() => fileName); 57 | 58 | try 59 | { 60 | using var stream = fileService.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None); 61 | Log.Debug($"Writing '{bytes.Length}' bytes to '{fileName}'"); 62 | 63 | stream.Write(bytes, 0, bytes.Length); 64 | } 65 | catch (Exception ex) 66 | { 67 | Log.Warning(ex, $"Failed to write '{bytes.Length}' bytes to '{fileName}'"); 68 | 69 | throw; 70 | } 71 | } 72 | 73 | public static async Task WriteAllBytesAsync(this IFileService fileService, string fileName, byte[] bytes) 74 | { 75 | ArgumentNullException.ThrowIfNull(fileService); 76 | Argument.IsNotNullOrWhitespace(() => fileName); 77 | 78 | try 79 | { 80 | await using var stream = fileService.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None); 81 | Log.Debug($"Writing '{bytes.Length}' bytes to '{fileName}'"); 82 | 83 | await stream.WriteAsync(bytes, 0, bytes.Length); 84 | } 85 | catch (Exception ex) 86 | { 87 | Log.Warning(ex, $"Failed to write '{bytes.Length}' bytes to '{fileName}'"); 88 | 89 | throw; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/Extensions/IFileServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.IO; 5 | using Catel.Logging; 6 | 7 | public static partial class IFileServiceExtensions 8 | { 9 | private static readonly ILog Log = LogManager.GetCurrentClassLogger(); 10 | 11 | public static bool CanOpenRead(this IFileService fileService, string fileName) 12 | { 13 | ArgumentNullException.ThrowIfNull(fileService); 14 | 15 | return fileService.CanOpen(fileName, FileMode.Open, FileAccess.Read); 16 | } 17 | 18 | public static Stream OpenRead(this IFileService fileService, string fileName) 19 | { 20 | ArgumentNullException.ThrowIfNull(fileService); 21 | 22 | return fileService.Open(fileName, FileMode.Open, FileAccess.Read); 23 | } 24 | 25 | public static bool CanOpenWrite(this IFileService fileService, string fileName) 26 | { 27 | ArgumentNullException.ThrowIfNull(fileService); 28 | 29 | return fileService.CanOpen(fileName, FileMode.Create, FileAccess.Write, FileShare.None); 30 | } 31 | 32 | public static Stream OpenWrite(this IFileService fileService, string fileName) 33 | { 34 | ArgumentNullException.ThrowIfNull(fileService); 35 | 36 | return fileService.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/Extensions/IFileServiceExtensions.lines.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Catel; 9 | using Catel.Logging; 10 | 11 | public static partial class IFileServiceExtensions 12 | { 13 | public static string[] ReadAllLines(this IFileService fileService, string fileName) 14 | { 15 | ArgumentNullException.ThrowIfNull(fileService); 16 | Argument.IsNotNullOrWhitespace(() => fileName); 17 | 18 | try 19 | { 20 | using var stream = fileService.OpenRead(fileName); 21 | Log.Debug($"Reading all lines from '{fileName}'"); 22 | 23 | using var reader = new StreamReader(stream); 24 | var text = reader.ReadToEnd(); 25 | 26 | var lines = text.Split(new [] { Environment.NewLine }, StringSplitOptions.None); 27 | return lines; 28 | } 29 | catch (Exception ex) 30 | { 31 | Log.Warning(ex, $"Failed to read all lines from '{fileName}'"); 32 | 33 | throw; 34 | } 35 | } 36 | 37 | public static async Task ReadAllLinesAsync(this IFileService fileService, string fileName) 38 | { 39 | ArgumentNullException.ThrowIfNull(fileService); 40 | Argument.IsNotNullOrWhitespace(() => fileName); 41 | 42 | try 43 | { 44 | await using var stream = fileService.OpenRead(fileName); 45 | Log.Debug($"Reading all lines from '{fileName}'"); 46 | 47 | using var reader = new StreamReader(stream); 48 | var text = await reader.ReadToEndAsync(); 49 | 50 | var lines = text.Split(new [] { Environment.NewLine }, StringSplitOptions.None); 51 | return lines; 52 | } 53 | catch (Exception ex) 54 | { 55 | Log.Warning(ex, $"Failed to read all lines from '{fileName}'"); 56 | 57 | throw; 58 | } 59 | } 60 | 61 | public static Task WriteAllLinesAsync(this IFileService fileService, string fileName, string[] lines) 62 | { 63 | return WriteAllLinesAsync(fileService, fileName, (IEnumerable)lines); 64 | } 65 | 66 | public static void WriteAllLines(this IFileService fileService, string fileName, IEnumerable lines) 67 | { 68 | ArgumentNullException.ThrowIfNull(fileService); 69 | Argument.IsNotNullOrWhitespace(() => fileName); 70 | 71 | var count = 0; 72 | 73 | try 74 | { 75 | count = lines.Count(); 76 | 77 | using var stream = fileService.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None); 78 | Log.Debug($"Writing '{count}' lines to '{fileName}'"); 79 | 80 | using var writer = new StreamWriter(stream); 81 | foreach (var line in lines) 82 | { 83 | writer.WriteLine(line); 84 | } 85 | } 86 | catch (Exception ex) 87 | { 88 | Log.Warning(ex, $"Failed to write '{count}' lines to '{fileName}'"); 89 | 90 | throw; 91 | } 92 | } 93 | 94 | public static async Task WriteAllLinesAsync(this IFileService fileService, string fileName, IEnumerable lines) 95 | { 96 | ArgumentNullException.ThrowIfNull(fileService); 97 | Argument.IsNotNullOrWhitespace(() => fileName); 98 | 99 | var count = 0; 100 | 101 | try 102 | { 103 | count = lines.Count(); 104 | 105 | await using var stream = fileService.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None); 106 | Log.Debug($"Writing '{count}' lines to '{fileName}'"); 107 | 108 | await using var writer = new StreamWriter(stream); 109 | foreach (var line in lines) 110 | { 111 | await writer.WriteLineAsync(line); 112 | } 113 | } 114 | catch (Exception ex) 115 | { 116 | Log.Warning(ex, $"Failed to write '{count}' lines to '{fileName}'"); 117 | 118 | throw; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/Extensions/IFileServiceExtensions.text.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | using Catel; 7 | using Catel.Logging; 8 | 9 | public static partial class IFileServiceExtensions 10 | { 11 | public static string ReadAllText(this IFileService fileService, string fileName) 12 | { 13 | ArgumentNullException.ThrowIfNull(fileService); 14 | Argument.IsNotNullOrWhitespace(() => fileName); 15 | 16 | try 17 | { 18 | using var stream = fileService.OpenRead(fileName); 19 | Log.Debug($"Reading all text from '{fileName}'"); 20 | 21 | using var reader = new StreamReader(stream); 22 | var text = reader.ReadToEnd(); 23 | return text; 24 | } 25 | catch (Exception ex) 26 | { 27 | Log.Warning(ex, $"Failed to read all text from '{fileName}'"); 28 | 29 | throw; 30 | } 31 | } 32 | 33 | public static async Task ReadAllTextAsync(this IFileService fileService, string fileName) 34 | { 35 | ArgumentNullException.ThrowIfNull(fileService); 36 | Argument.IsNotNullOrWhitespace(() => fileName); 37 | 38 | try 39 | { 40 | await using var stream = fileService.OpenRead(fileName); 41 | Log.Debug($"Reading all text from '{fileName}'"); 42 | 43 | using var reader = new StreamReader(stream); 44 | var text = await reader.ReadToEndAsync(); 45 | return text; 46 | } 47 | catch (Exception ex) 48 | { 49 | Log.Warning(ex, $"Failed to read all text from '{fileName}'"); 50 | 51 | throw; 52 | } 53 | } 54 | 55 | public static void WriteAllText(this IFileService fileService, string fileName, string text) 56 | { 57 | ArgumentNullException.ThrowIfNull(fileService); 58 | Argument.IsNotNullOrWhitespace(() => fileName); 59 | 60 | try 61 | { 62 | using var stream = fileService.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None); 63 | Log.Debug($"Writing text to '{fileName}'"); 64 | 65 | using var writer = new StreamWriter(stream); 66 | writer.Write(text); 67 | } 68 | catch (Exception ex) 69 | { 70 | Log.Warning(ex, $"Failed to write text to '{fileName}'"); 71 | 72 | throw; 73 | } 74 | } 75 | 76 | public static async Task WriteAllTextAsync(this IFileService fileService, string fileName, string text) 77 | { 78 | ArgumentNullException.ThrowIfNull(fileService); 79 | Argument.IsNotNullOrWhitespace(() => fileName); 80 | 81 | try 82 | { 83 | await using var stream = fileService.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None); 84 | Log.Debug($"Writing text to '{fileName}'"); 85 | 86 | await using var writer = new StreamWriter(stream); 87 | await writer.WriteAsync(text); 88 | } 89 | catch (Exception ex) 90 | { 91 | Log.Warning(ex, $"Failed to write text to '{fileName}'"); 92 | 93 | throw; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/Interfaces/IDirectoryService.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System.IO; 4 | 5 | public interface IDirectoryService 6 | { 7 | string Create(string path); 8 | void Move(string sourcePath, string destinationPath); 9 | void Delete(string path, bool recursive = true); 10 | bool Exists(string path); 11 | string[] GetDirectories(string path, string searchPattern = "", SearchOption searchOption = SearchOption.TopDirectoryOnly); 12 | string[] GetFiles(string path, string searchPattern = "", SearchOption searchOption = SearchOption.TopDirectoryOnly); 13 | void Copy(string sourcePath, string destinationPath, bool copySubDirs = true, bool overwriteExisting = false); 14 | } -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/Interfaces/IFileService.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System.IO; 4 | 5 | public interface IFileService 6 | { 7 | Stream Create(string fileName); 8 | void Copy(string sourceFileName, string destinationFileName, bool overwrite = false); 9 | void Move(string sourceFileName, string destinationFileName, bool overwrite = false); 10 | bool Exists(string fileName); 11 | void Delete(string fileName); 12 | Stream Open(string fileName, FileMode fileMode, FileAccess fileAccess = FileAccess.ReadWrite, FileShare fileShare = FileShare.ReadWrite); 13 | bool CanOpen(string fileName, FileMode fileMode, FileAccess fileAccess = FileAccess.ReadWrite, FileShare fileShare = FileShare.ReadWrite); 14 | } -------------------------------------------------------------------------------- /src/Orc.FileSystem/Services/Interfaces/IIOSynchronizationService.cs: -------------------------------------------------------------------------------- 1 | namespace Orc.FileSystem; 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | public interface IIOSynchronizationService 7 | { 8 | TimeSpan DelayBetweenChecks { get; set; } 9 | TimeSpan DelayAfterWriteOperations { get; set; } 10 | 11 | event EventHandler? RefreshRequired; 12 | 13 | IDisposable AcquireReadLock(string path); 14 | IDisposable AcquireWriteLock(string path, bool notifyOnRelease = true); 15 | 16 | Task StartWatchingForChangesAsync(string path); 17 | Task StopWatchingForChangesAsync(string path); 18 | 19 | Task ExecuteReadingAsync(string projectLocation, Func> readAsync); 20 | Task ExecuteWritingAsync(string projectLocation, Func> writeAsync); 21 | } -------------------------------------------------------------------------------- /src/SolutionAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by Cake. 4 | // 5 | //------------------------------------------------------------------------------ 6 | using System.Reflection; 7 | 8 | [assembly: AssemblyCompany("WildGums")] 9 | [assembly: AssemblyVersion("5.0.0")] 10 | [assembly: AssemblyFileVersion("5.0.0")] 11 | [assembly: AssemblyInformationalVersion("5.0.0-beta.1+131")] 12 | [assembly: AssemblyCopyright("Copyright © WildGums 2014 - 2020")] 13 | 14 | -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild-sdks": { 3 | "MSBuild.Sdk.Extras": "3.0.44" 4 | }, 5 | "sdk": { 6 | "version": "9.0.0", 7 | "rollForward": "latestMinor", 8 | "allowPrerelease": false 9 | } 10 | } -------------------------------------------------------------------------------- /src/nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tools/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WildGums/Orc.FileSystem/14c902160c6ccba8260d9541c43fb99880e48d33/tools/nuget.exe --------------------------------------------------------------------------------