├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── buildGUI.yml ├── .gitignore ├── LICENSE ├── MailDisk-GUI ├── MailDisk-GUI.sln └── MailDisk-GUI │ ├── App.config │ ├── App.xaml │ ├── App.xaml.cs │ ├── FodyWeavers.xml │ ├── FodyWeavers.xsd │ ├── MailDisk-GUI.csproj │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings │ ├── languages │ ├── en-US.xaml │ └── zh-CN.xaml │ ├── mailico.ico │ └── packages.config ├── README.md ├── maildisk ├── maildisk.sln └── maildisk │ ├── Program.cs │ ├── apis │ ├── MailClient.cs │ ├── Settings.cs │ └── VisualDisk.cs │ └── maildisk.csproj └── pictures ├── big.psd ├── mail.psd └── mailico.psd /.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: # Replace with a single Open Collective username 6 | ko_fi: chenxuuu 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'maildisk/**' 7 | - '.github/workflows/build.yml' 8 | pull_request: 9 | paths: 10 | - 'maildisk/**' 11 | workflow_dispatch: 12 | inputs: 13 | logLevel: 14 | description: 'Log level' 15 | required: true 16 | default: 'warning' 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v1 23 | - name: environment prepare 24 | run: | 25 | sudo apt-get update 26 | sudo apt-get install -y apt-transport-https 27 | sudo apt-get update 28 | sudo apt-get install -y dotnet-sdk-5.0 29 | sudo apt-get install -y p7zip-full 30 | - name: build 31 | run: | 32 | cd maildisk 33 | dotnet publish -r win-x86 -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true 34 | dotnet publish -r win-x64 -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true 35 | dotnet publish -r win-arm -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true 36 | dotnet publish -r win-arm64 -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true 37 | dotnet publish -r linux-x64 -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true 38 | dotnet publish -r linux-arm -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true 39 | dotnet publish -r linux-arm64 -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true 40 | dotnet publish -r osx-x64 -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true 41 | - name: create packages 42 | run: | 43 | mkdir maildisk-pkg 44 | mv maildisk/maildisk/bin/Debug/net5.0/win-x86/publish/* maildisk-pkg/ 45 | 7z a win-x86.7z maildisk-pkg/* 46 | rm maildisk-pkg/* 47 | mv maildisk/maildisk/bin/Debug/net5.0/win-x64/publish/* maildisk-pkg/ 48 | 7z a win-x64.7z maildisk-pkg/* 49 | rm maildisk-pkg/* 50 | mv maildisk/maildisk/bin/Debug/net5.0/win-arm/publish/* maildisk-pkg/ 51 | 7z a win-arm.7z maildisk-pkg/* 52 | rm maildisk-pkg/* 53 | mv maildisk/maildisk/bin/Debug/net5.0/win-arm64/publish/* maildisk-pkg/ 54 | 7z a win-arm64.7z maildisk-pkg/* 55 | rm maildisk-pkg/* 56 | mv maildisk/maildisk/bin/Debug/net5.0/linux-x64/publish/* maildisk-pkg/ 57 | 7z a linux-x64.7z maildisk-pkg/* 58 | rm maildisk-pkg/* 59 | mv maildisk/maildisk/bin/Debug/net5.0/linux-arm/publish/* maildisk-pkg/ 60 | 7z a linux-arm.7z maildisk-pkg/* 61 | rm maildisk-pkg/* 62 | mv maildisk/maildisk/bin/Debug/net5.0/linux-arm64/publish/* maildisk-pkg/ 63 | 7z a linux-arm64.7z maildisk-pkg/* 64 | rm maildisk-pkg/* 65 | mv maildisk/maildisk/bin/Debug/net5.0/osx-x64/publish/* maildisk-pkg/ 66 | 7z a osx-x64.7z maildisk-pkg/* 67 | - uses: actions/upload-artifact@v2 68 | with: 69 | name: artifact 70 | path: | 71 | win-x86.7z 72 | win-x64.7z 73 | win-arm.7z 74 | win-arm64.7z 75 | linux-x64.7z 76 | linux-arm.7z 77 | linux-arm64.7z 78 | osx-x64.7z 79 | -------------------------------------------------------------------------------- /.github/workflows/buildGUI.yml: -------------------------------------------------------------------------------- 1 | name: Build GUI client 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'MailDisk-GUI/**' 7 | - '.github/workflows/buildGUI.yml' 8 | pull_request: 9 | paths: 10 | - 'MailDisk-GUI/**' 11 | workflow_dispatch: 12 | inputs: 13 | logLevel: 14 | description: 'Log level' 15 | required: true 16 | default: 'warning' 17 | 18 | jobs: 19 | build: 20 | runs-on: windows-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | - name: Add msbuild to PATH 25 | uses: microsoft/setup-msbuild@v1.0.2 26 | - name: Build 27 | run: | 28 | cd MailDisk-GUI 29 | nuget restore 30 | msbuild MailDisk-GUI.sln /p:Configuration=Release /p:DeployOnBuild=true /p:PublishProfile=FolderProfile 31 | - name: Upload Artifact 32 | uses: actions/upload-artifact@v2 33 | with: 34 | name: MailDisk-GUI 35 | path: MailDisk-GUI/MailDisk-GUI/bin/Release 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/visualstudio 2 | 3 | ### VisualStudio ### 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | ## 7 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 8 | 9 | # User-specific files 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # .NET Core 49 | project.lock.json 50 | project.fragment.lock.json 51 | artifacts/ 52 | **/Properties/launchSettings.json 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # TFS 2012 Local Workspace 100 | $tf/ 101 | 102 | # Guidance Automation Toolkit 103 | *.gpState 104 | 105 | # ReSharper is a .NET coding add-in 106 | _ReSharper*/ 107 | *.[Rr]e[Ss]harper 108 | *.DotSettings.user 109 | 110 | # JustCode is a .NET coding add-in 111 | .JustCode 112 | 113 | # TeamCity is a build add-in 114 | _TeamCity* 115 | 116 | # DotCover is a Code Coverage Tool 117 | *.dotCover 118 | 119 | # Visual Studio code coverage results 120 | *.coverage 121 | *.coveragexml 122 | 123 | # NCrunch 124 | _NCrunch_* 125 | .*crunch*.local.xml 126 | nCrunchTemp_* 127 | 128 | # MightyMoose 129 | *.mm.* 130 | AutoTest.Net/ 131 | 132 | # Web workbench (sass) 133 | .sass-cache/ 134 | 135 | # Installshield output folder 136 | [Ee]xpress/ 137 | 138 | # DocProject is a documentation generator add-in 139 | DocProject/buildhelp/ 140 | DocProject/Help/*.HxT 141 | DocProject/Help/*.HxC 142 | DocProject/Help/*.hhc 143 | DocProject/Help/*.hhk 144 | DocProject/Help/*.hhp 145 | DocProject/Help/Html2 146 | DocProject/Help/html 147 | 148 | # Click-Once directory 149 | publish/ 150 | 151 | # Publish Web Output 152 | *.[Pp]ublish.xml 153 | *.azurePubxml 154 | # TODO: Comment the next line if you want to checkin your web deploy settings 155 | # but database connection strings (with potential passwords) will be unencrypted 156 | *.pubxml 157 | *.publishproj 158 | 159 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 160 | # checkin your Azure Web App publish settings, but sensitive information contained 161 | # in these scripts will be unencrypted 162 | PublishScripts/ 163 | 164 | # NuGet Packages 165 | *.nupkg 166 | # The packages folder can be ignored because of Package Restore 167 | **/packages/* 168 | # except build/, which is used as an MSBuild target. 169 | !**/packages/build/ 170 | # Uncomment if necessary however generally it will be regenerated when needed 171 | #!**/packages/repositories.config 172 | # NuGet v3's project.json files produces more ignorable files 173 | *.nuget.props 174 | *.nuget.targets 175 | 176 | # Microsoft Azure Build Output 177 | csx/ 178 | *.build.csdef 179 | 180 | # Microsoft Azure Emulator 181 | ecf/ 182 | rcf/ 183 | 184 | # Windows Store app package directories and files 185 | AppPackages/ 186 | BundleArtifacts/ 187 | Package.StoreAssociation.xml 188 | _pkginfo.txt 189 | 190 | # Visual Studio cache files 191 | # files ending in .cache can be ignored 192 | *.[Cc]ache 193 | # but keep track of directories ending in .cache 194 | !*.[Cc]ache/ 195 | 196 | # Others 197 | ClientBin/ 198 | ~$* 199 | *~ 200 | *.dbmdl 201 | *.dbproj.schemaview 202 | *.jfm 203 | *.pfx 204 | *.publishsettings 205 | orleans.codegen.cs 206 | 207 | # Since there are multiple workflows, uncomment next line to ignore bower_components 208 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 209 | #bower_components/ 210 | 211 | # RIA/Silverlight projects 212 | Generated_Code/ 213 | 214 | # Backup & report files from converting an old project file 215 | # to a newer Visual Studio version. Backup files are not needed, 216 | # because we have git ;-) 217 | _UpgradeReport_Files/ 218 | Backup*/ 219 | UpgradeLog*.XML 220 | UpgradeLog*.htm 221 | 222 | # SQL Server files 223 | *.mdf 224 | *.ldf 225 | 226 | # Business Intelligence projects 227 | *.rdl.data 228 | *.bim.layout 229 | *.bim_*.settings 230 | 231 | # Microsoft Fakes 232 | FakesAssemblies/ 233 | 234 | # GhostDoc plugin setting file 235 | *.GhostDoc.xml 236 | 237 | # Node.js Tools for Visual Studio 238 | .ntvs_analysis.dat 239 | node_modules/ 240 | 241 | # Typescript v1 declaration files 242 | typings/ 243 | 244 | # Visual Studio 6 build log 245 | *.plg 246 | 247 | # Visual Studio 6 workspace options file 248 | *.opt 249 | 250 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 251 | *.vbw 252 | 253 | # Visual Studio LightSwitch build output 254 | **/*.HTMLClient/GeneratedArtifacts 255 | **/*.DesktopClient/GeneratedArtifacts 256 | **/*.DesktopClient/ModelManifest.xml 257 | **/*.Server/GeneratedArtifacts 258 | **/*.Server/ModelManifest.xml 259 | _Pvt_Extensions 260 | 261 | # Paket dependency manager 262 | .paket/paket.exe 263 | paket-files/ 264 | 265 | # FAKE - F# Make 266 | .fake/ 267 | 268 | # JetBrains Rider 269 | .idea/ 270 | *.sln.iml 271 | 272 | # CodeRush 273 | .cr/ 274 | 275 | # Python Tools for Visual Studio (PTVS) 276 | __pycache__/ 277 | *.pyc 278 | 279 | # Cake - Uncomment if you are using it 280 | # tools/** 281 | # !tools/packages.config 282 | 283 | # End of https://www.gitignore.io/api/visualstudio -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "{}" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2017 晨旭 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | 193 | 194 | The MIT License 195 | 196 | Copyright (c) 2012-2014 Torben Könke 197 | 198 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 199 | 200 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 201 | 202 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailDisk-GUI", "MailDisk-GUI\MailDisk-GUI.csproj", "{B21FA876-FD48-42F0-8B2B-A08B24671DB9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B21FA876-FD48-42F0-8B2B-A08B24671DB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {B21FA876-FD48-42F0-8B2B-A08B24671DB9}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {B21FA876-FD48-42F0-8B2B-A08B24671DB9}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {B21FA876-FD48-42F0-8B2B-A08B24671DB9}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {99ABBE37-373C-4151-9CC1-8F0A99D7C420} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace MailDisk_GUI 10 | { 11 | /// 12 | /// App.xaml 的交互逻辑 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Used to control if the On_PropertyName_Changed feature is enabled. 12 | 13 | 14 | 15 | 16 | Used to control if the Dependent properties feature is enabled. 17 | 18 | 19 | 20 | 21 | Used to control if the IsChanged property feature is enabled. 22 | 23 | 24 | 25 | 26 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. 27 | 28 | 29 | 30 | 31 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. 32 | 33 | 34 | 35 | 36 | Used to control if equality checks should use the Equals method resolved from the base class. 37 | 38 | 39 | 40 | 41 | Used to control if equality checks should use the static Equals method resolved from the base class. 42 | 43 | 44 | 45 | 46 | Used to turn off build warnings from this weaver. 47 | 48 | 49 | 50 | 51 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods. 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks 62 | 63 | 64 | 65 | 66 | A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. 67 | 68 | 69 | 70 | 71 | A list of unmanaged 32 bit assembly names to include, delimited with line breaks. 72 | 73 | 74 | 75 | 76 | A list of unmanaged 64 bit assembly names to include, delimited with line breaks. 77 | 78 | 79 | 80 | 81 | The order of preloaded assemblies, delimited with line breaks. 82 | 83 | 84 | 85 | 86 | 87 | This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. 88 | 89 | 90 | 91 | 92 | Controls if .pdbs for reference assemblies are also embedded. 93 | 94 | 95 | 96 | 97 | Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. 98 | 99 | 100 | 101 | 102 | As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. 103 | 104 | 105 | 106 | 107 | Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. 108 | 109 | 110 | 111 | 112 | Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. 113 | 114 | 115 | 116 | 117 | A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | 118 | 119 | 120 | 121 | 122 | A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. 123 | 124 | 125 | 126 | 127 | A list of unmanaged 32 bit assembly names to include, delimited with |. 128 | 129 | 130 | 131 | 132 | A list of unmanaged 64 bit assembly names to include, delimited with |. 133 | 134 | 135 | 136 | 137 | The order of preloaded assemblies, delimited with |. 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 146 | 147 | 148 | 149 | 150 | A comma-separated list of error codes that can be safely ignored in assembly verification. 151 | 152 | 153 | 154 | 155 | 'false' to turn off automatic generation of the XML Schema file. 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/MailDisk-GUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {B21FA876-FD48-42F0-8B2B-A08B24671DB9} 10 | WinExe 11 | MailDisk_GUI 12 | MailDisk-GUI 13 | v4.6.2 14 | 512 15 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | 4 17 | true 18 | true 19 | 20 | 21 | 22 | 23 | AnyCPU 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | AnyCPU 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | mailico.ico 43 | 44 | 45 | 46 | ..\packages\Costura.Fody.4.1.0\lib\net40\Costura.dll 47 | 48 | 49 | ..\packages\FontAwesome.WPF.4.7.0.9\lib\net40\FontAwesome.WPF.dll 50 | 51 | 52 | ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll 53 | 54 | 55 | ..\packages\PropertyChanged.Fody.3.3.1\lib\net40\PropertyChanged.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 4.0 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | MSBuild:Compile 75 | Designer 76 | 77 | 78 | MSBuild:Compile 79 | Designer 80 | 81 | 82 | Designer 83 | MSBuild:Compile 84 | 85 | 86 | MSBuild:Compile 87 | Designer 88 | 89 | 90 | App.xaml 91 | Code 92 | 93 | 94 | MainWindow.xaml 95 | Code 96 | 97 | 98 | 99 | 100 | Code 101 | 102 | 103 | True 104 | True 105 | Resources.resx 106 | 107 | 108 | True 109 | Settings.settings 110 | True 111 | 112 | 113 | ResXFileCodeGenerator 114 | Resources.Designer.cs 115 | 116 | 117 | 118 | SettingsSingleFileGenerator 119 | Settings.Designer.cs 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 9.0 141 | 142 | 143 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace MailDisk_GUI 17 | { 18 | /// 19 | /// MainWindow.xaml 的交互逻辑 20 | /// 21 | public partial class MainWindow : Window 22 | { 23 | public MainWindow() 24 | { 25 | InitializeComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // 有关程序集的一般信息由以下 8 | // 控制。更改这些特性值可修改 9 | // 与程序集关联的信息。 10 | [assembly: AssemblyTitle("MailDisk-GUI")] 11 | [assembly: AssemblyDescription("邮箱网盘")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("chenxublog.com")] 14 | [assembly: AssemblyProduct("MailDisk-GUI")] 15 | [assembly: AssemblyCopyright("Copyright © chenxuuu")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // 将 ComVisible 设置为 false 会使此程序集中的类型 20 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 21 | //请将此类型的 ComVisible 特性设置为 true。 22 | [assembly: ComVisible(false)] 23 | 24 | //若要开始生成可本地化的应用程序,请设置 25 | //.csproj 文件中的 CultureYouAreCodingWith 26 | //例如,如果您在源文件中使用的是美国英语, 27 | //使用的是美国英语,请将 设置为 en-US。 然后取消 28 | //对以下 NeutralResourceLanguage 特性的注释。 更新 29 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置 36 | //(未在页面中找到资源时使用, 37 | //或应用程序资源字典中找到时使用) 38 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 39 | //(未在页面中找到资源时使用, 40 | //、应用程序或任何主题专用资源字典中找到时使用) 41 | )] 42 | 43 | 44 | // 程序集的版本信息由下列四个值组成: 45 | // 46 | // 主版本 47 | // 次版本 48 | // 生成号 49 | // 修订号 50 | // 51 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 52 | //通过使用 "*",如下所示: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本: 4.0.30319.42000 5 | // 6 | // 对此文件的更改可能导致不正确的行为,如果 7 | // 重新生成代码,则所做更改将丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | 12 | namespace MailDisk_GUI.Properties 13 | { 14 | /// 15 | /// 强类型资源类,用于查找本地化字符串等。 16 | /// 17 | // 此类是由 StronglyTypedResourceBuilder 18 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 19 | // 若要添加或删除成员,请编辑 .ResX 文件,然后重新运行 ResGen 20 | // (以 /str 作为命令选项),或重新生成 VS 项目。 21 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 22 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 23 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 24 | internal class Resources 25 | { 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 | /// 37 | /// 返回此类使用的缓存 ResourceManager 实例。 38 | /// 39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 40 | internal static global::System.Resources.ResourceManager ResourceManager 41 | { 42 | get 43 | { 44 | if ((resourceMan == null)) 45 | { 46 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MailDisk_GUI.Properties.Resources", typeof(Resources).Assembly); 47 | resourceMan = temp; 48 | } 49 | return resourceMan; 50 | } 51 | } 52 | 53 | /// 54 | /// 重写当前线程的 CurrentUICulture 属性,对 55 | /// 使用此强类型资源类的所有资源查找执行重写。 56 | /// 57 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 58 | internal static global::System.Globalization.CultureInfo Culture 59 | { 60 | get 61 | { 62 | return resourceCulture; 63 | } 64 | set 65 | { 66 | resourceCulture = value; 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/Properties/Settings.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 | 12 | namespace MailDisk_GUI.Properties 13 | { 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 17 | { 18 | 19 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 20 | 21 | public static Settings Default 22 | { 23 | get 24 | { 25 | return defaultInstance; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/languages/en-US.xaml: -------------------------------------------------------------------------------- 1 | 5 | MailDisk 6 | Files 7 | Jobs 8 | Settings 9 | About 10 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/languages/zh-CN.xaml: -------------------------------------------------------------------------------- 1 | 5 | 邮箱网盘 6 | 文件 7 | 任务 8 | 设置 9 | 关于 10 | -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/mailico.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuuu/Mail-Box-Net-Disk/dcb1222d01c5596fd6073898f37650a31f35292b/MailDisk-GUI/MailDisk-GUI/mailico.ico -------------------------------------------------------------------------------- /MailDisk-GUI/MailDisk-GUI/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 邮箱网盘 2 | 3 | ![build](https://github.com/chenxuuu/Mail-Box-Net-Disk/workflows/build/badge.svg) 4 | ![Build GUI client](https://github.com/chenxuuu/Mail-Box-Net-Disk/workflows/Build%20GUI%20client/badge.svg) 5 | 6 | 利用邮箱实现网盘功能的工具。 7 | 8 | 本项目有两个仓库,不过建议在GitHub进行star/pr操作: 9 | 10 | GitHub:[https://github.com/chenxuuu/Mail-Box-Net-Disk](https://github.com/chenxuuu/Mail-Box-Net-Disk) 11 | 12 | git.osc:[https://gitee.com/chenxuuu/Mail-Box-Net-Disk](https://gitee.com/chenxuuu/Mail-Box-Net-Disk) 13 | 14 | ## 下载 15 | 16 | 每次发布版本,会自动在GitHub的release页更新:[点击前往](https://github.com/chenxuuu/Mail-Box-Net-Disk/releases/latest) 17 | 18 | ## 原理 19 | 20 | 利用邮箱附件作为文件存储空间,实现文件的上传下载功能 21 | 22 | ## 功能 23 | 24 | - [x] 支持smtp/imap协议的邮箱 25 | - [x] 读取文件夹列表/新建文件夹 26 | - [x] 上传小于限制大小的文件 27 | - [x] 读取已存在的文件 28 | - [x] 上传大文件自动分卷 29 | - [x] 识别分卷上传的文件,下载自动合并 30 | - [x] 优化文件搜索速度 31 | - [x] 完成命令行工具成品 32 | - [x] 支持文件夹上传 33 | - [x] 支持文件夹下载 34 | - [ ] win系统下的gui管理工具 35 | - [ ] 其他系统下的gui管理工具 36 | 37 | ## 命令列表 38 | 39 | ```cmd 40 | maildisk -h 查看命令帮助 41 | 42 | -s 43 | 更改邮箱参数设置 44 | 45 | -lf 46 | 列出邮箱的所有邮件文件夹 47 | 48 | -cf <邮件文件夹名> 49 | 新建一个邮件文件夹 50 | 51 | -l <邮件文件夹> 52 | 列出所有该邮件文件夹下的文件 53 | 54 | -c <邮件文件夹> 55 | 清除所有该邮件文件夹下不完整的分卷文件 56 | 57 | -u <邮件文件夹> <本地文件> <云端文件> 58 | 上传文件 59 | 60 | -d <邮件文件夹> <本地文件> <云端文件> 61 | 下载文件 62 | 63 | -uf <邮件文件夹> <本地文件夹> <云端虚拟文件夹路径> 64 | 上传文件夹,并清理不完整分卷的邮件 65 | 注意:如果云端存在该文件,则不会上传 66 | 67 | -df <邮件文件夹> <本地文件夹> <云端虚拟文件夹路径> 68 | 下载文件夹 69 | 70 | 所有文件和路径都不能包含'<'符号 71 | ``` 72 | 73 | ## 加入该项目 74 | 75 | 如果你愿意忍受我写的智障代码,那么欢迎pr 76 | 77 | ## 其他 78 | 79 | 该项目的代码可以随意引用,但请保留指向该项目的说明文字 80 | -------------------------------------------------------------------------------- /maildisk/maildisk.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "maildisk", "maildisk\maildisk.csproj", "{40D245C7-6840-4A9B-AEA9-4F9503013CC0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {40D245C7-6840-4A9B-AEA9-4F9503013CC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {40D245C7-6840-4A9B-AEA9-4F9503013CC0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {40D245C7-6840-4A9B-AEA9-4F9503013CC0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {40D245C7-6840-4A9B-AEA9-4F9503013CC0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {6FBAA721-B24C-48A4-AA71-CA4D6CD539C2} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /maildisk/maildisk/Program.cs: -------------------------------------------------------------------------------- 1 | using MailKit; 2 | using MailKit.Net.Imap; 3 | using MailKit.Search; 4 | using System; 5 | using maildisk.apis; 6 | using System.IO; 7 | using System.Text; 8 | 9 | namespace maildisk 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | if(args.Length > 0) 16 | switch(args[0]) 17 | { 18 | case "-h": 19 | Console.WriteLine(@" 20 | *********** 21 | *Mail Disk* 22 | *********** 23 | You can use these commands: 24 | 25 | -h: 26 | show commands we support. 27 | 28 | -s: 29 | set your imap settings. 30 | 31 | -lf: 32 | list all folders on mail server. 33 | 34 | -cf : 35 | create new folder on mail server. 36 | 37 | -l : 38 | show files in this folder. 39 | 40 | -c : 41 | clear all wrong files in this folder. 42 | 43 | -u : 44 | upload a file to net disk. 45 | 46 | -d : 47 | download a file from net disk. 48 | 49 | -uf : 50 | upload a folder to net disk and clear all wrong files in this email folder. 51 | Notice: if file is exist on cloud, it will not be uploaded. 52 | 53 | -df : 54 | download a folder from net disk. 55 | Notice: local file must be not exist. 56 | 57 | All file and path should not contain '<'".Replace("\r\n","\r\n\t")); 58 | return; 59 | 60 | case "-s": 61 | Settings.Set(); 62 | return; 63 | 64 | case "-lf": 65 | var lfdisk = Settings.GetDisk(); 66 | if (lfdisk == null) return; 67 | Console.WriteLine("getting all folders ..."); 68 | var all = lfdisk.GetFolders(); 69 | Console.WriteLine("here's all folders:"); 70 | foreach(var f in all) 71 | { 72 | Console.WriteLine(f.FullName); 73 | } 74 | return; 75 | 76 | case "-cf": 77 | var cfdisk = Settings.GetDisk(); 78 | if (cfdisk == null) return; 79 | if(args.Length < 2) { Console.WriteLine("please enter a folder name");return; } 80 | Console.WriteLine($"creating folder {args[1]} ..."); 81 | cfdisk.CreatFolder(args[1]); 82 | return; 83 | 84 | case "-l": 85 | var ldisk = Settings.GetDisk(); 86 | if (ldisk == null) return; 87 | if (args.Length < 2) { Console.WriteLine("please enter a folder name"); return; } 88 | Console.WriteLine($"fetching file list with folder {args[1]} ..."); 89 | var lfiles = ldisk.GetFileList(args[1]); 90 | Console.WriteLine($"\r\n\r\ndone! list of files:"); 91 | foreach (var s in lfiles) 92 | { 93 | Console.WriteLine(s); 94 | } 95 | return; 96 | 97 | case "-c": 98 | var cdisk = Settings.GetDisk(); 99 | if (cdisk == null) return; 100 | if (args.Length < 2) { Console.WriteLine("please enter a folder name"); return; } 101 | Console.WriteLine($"fetching file list with folder {args[1]} ..."); 102 | cdisk.GetFileList(args[1], true); 103 | Console.WriteLine($"\r\n\r\ndone!"); 104 | return; 105 | 106 | case "-u": 107 | var udisk = Settings.GetDisk(); 108 | if (udisk == null) return; 109 | if (args.Length < 4) { Console.WriteLine("wrong args count"); return; } 110 | Console.WriteLine($"uploading file {args[2]} to {args[1]} as {args[3]} ..."); 111 | if(args[3].IndexOf("<")>=0) 112 | { 113 | Console.WriteLine($"error! file name do not contain '<'"); 114 | return; 115 | } 116 | udisk.UploadBigFile(args[3], args[1], args[2], (int)Settings.maxBlock * 1024 * 1024); 117 | return; 118 | 119 | case "-d": 120 | var ddisk = Settings.GetDisk(); 121 | if (ddisk == null) return; 122 | if (args.Length < 4) { Console.WriteLine("wrong args count"); return; } 123 | Console.WriteLine($"Download file {args[3]} from {args[1]} as {args[2]} ..."); 124 | if (args[3].IndexOf("<") >= 0) 125 | { 126 | Console.WriteLine($"error! file name do not contain '<'"); 127 | return; 128 | } 129 | ddisk.DownloadFile(args[1], args[3], args[2]); 130 | return; 131 | 132 | case "-uf": 133 | var ufdisk = Settings.GetDisk(); 134 | if (ufdisk == null) return; 135 | if (args.Length < 4) { Console.WriteLine("wrong args count"); return; } 136 | Console.WriteLine($"upload folder {args[3]} to {args[1]} as {args[2]} ..."); 137 | if (args[3].IndexOf("<") >= 0) 138 | { 139 | Console.WriteLine($"error! folder name do not contain '<'"); 140 | return; 141 | } 142 | ufdisk.RefreshFiles(args[1]); 143 | ufdisk.UploadFolder(args[3], args[1], args[2], (int)Settings.maxBlock * 1024 * 1024); 144 | Console.WriteLine("done! all files uploaded!"); 145 | return; 146 | 147 | case "-df": 148 | var dfdisk = Settings.GetDisk(); 149 | if (dfdisk == null) return; 150 | if (args.Length < 4) { Console.WriteLine("wrong args count"); return; } 151 | Console.WriteLine($"Download folder {args[3]} from {args[1]} as {args[2]} ..."); 152 | if (args[3].IndexOf("<") >= 0) 153 | { 154 | Console.WriteLine($"error! folder name do not contain '<'"); 155 | return; 156 | } 157 | dfdisk.DownloadFolder(args[3], args[1], args[2]); 158 | Console.WriteLine("done! all files downloaded!"); 159 | return; 160 | 161 | default: 162 | break; 163 | } 164 | Console.WriteLine(@"no commond matched 165 | use -h to show commands we support"); 166 | return; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /maildisk/maildisk/apis/MailClient.cs: -------------------------------------------------------------------------------- 1 | using MailKit; 2 | using MailKit.Net.Imap; 3 | using MailKit.Search; 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace maildisk.apis 10 | { 11 | class MailClient 12 | { 13 | private string address; 14 | private ImapClient client = new ImapClient(); 15 | 16 | /// 17 | /// new mail client 18 | /// 19 | /// server address 20 | /// server port 21 | /// server use ssl or not 22 | /// your account 23 | /// your password 24 | /// your email address 25 | public MailClient(string server, int port, bool useSsl, string account, string password, string address) 26 | { 27 | client.ServerCertificateValidationCallback = (s, c, h, e) => true; 28 | client.Connect(server, port, useSsl); 29 | client.Authenticate(account, password); 30 | this.address = address; 31 | #if DEBUG 32 | Console.WriteLine($"[mail create]mail client created, on {server}:{port}" + 33 | $", ssl:{useSsl}, {account}, {address}"); 34 | #endif 35 | } 36 | 37 | /// 38 | /// get all folders in this mail 39 | /// 40 | /// path, default is empty 41 | /// folder list 42 | public IMailFolder[] GetFolders(string path = "") 43 | { 44 | ArrayList folders = new ArrayList(); 45 | 46 | var personal = client.GetFolder(path); 47 | 48 | foreach (var folder in personal.GetSubfolders(false)) 49 | { 50 | if (folder.GetSubfolders(false).Count > 0) 51 | { 52 | #if DEBUG 53 | Console.WriteLine($"[folder open] {path + folder.Name}"); 54 | #endif 55 | folders.AddRange(GetFolders(folder.FullName)); 56 | } 57 | else 58 | { 59 | folders.Add(folder); 60 | #if DEBUG 61 | Console.WriteLine($"[folder get] {folder.FullName}"); 62 | #endif 63 | } 64 | } 65 | return (IMailFolder[])folders.ToArray(typeof(IMailFolder)); 66 | } 67 | 68 | /// 69 | /// get one folder 70 | /// 71 | /// path, default is empty 72 | /// folder 73 | public IMailFolder GetFolder(string path) 74 | { 75 | return client.GetFolder(path); 76 | } 77 | 78 | 79 | /// 80 | /// get not read mails, and mark as seen 81 | /// 82 | /// mails' IMessageSummary 83 | public IList GetNotSeen() 84 | { 85 | ArrayList mails = new ArrayList(); 86 | var inbox = client.Inbox; 87 | inbox.Open(FolderAccess.ReadWrite); 88 | var uids = inbox.Search(SearchQuery.NotSeen); 89 | inbox.AddFlags(uids, MessageFlags.Seen, true); 90 | #if DEBUG 91 | Console.WriteLine($"[GetNotSeen] mails count {uids.Count}"); 92 | #endif 93 | return inbox.Fetch(uids, 94 | MessageSummaryItems.Full | MessageSummaryItems.UniqueId); 95 | } 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /maildisk/maildisk/apis/Settings.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Text; 8 | 9 | namespace maildisk.apis 10 | { 11 | class Settings 12 | { 13 | public static long maxBlock = 0; 14 | 15 | private static string GetBasePath() 16 | { 17 | using var processModule = Process.GetCurrentProcess().MainModule; 18 | return Path.GetDirectoryName(processModule?.FileName); 19 | } 20 | 21 | /// 22 | /// check setting file exist 23 | /// 24 | /// file exist 25 | public static bool CheckSetings() 26 | { 27 | return File.Exists(GetBasePath() + "/mail.json"); 28 | } 29 | 30 | /// 31 | /// create settings file 32 | /// 33 | public static void Set() 34 | { 35 | Console.WriteLine("Notice: If you're using QQMail, pleause create a login code on mail.qq.com\r\n"); 36 | JObject o = new JObject(); 37 | Console.Write("Enter the imap server address:"); 38 | o["imap"] = Console.ReadLine(); 39 | 40 | Console.Write("Enter the imap server port(default 993):"); 41 | try { o["port"] = int.Parse(Console.ReadLine()); } 42 | catch { o["port"] = 993; } 43 | 44 | Console.Write("Use ssl?(y/n):"); 45 | o["ssl"] = Console.ReadLine() == "y" ? true : false; 46 | 47 | Console.Write("Account:"); 48 | o["account"] = Console.ReadLine(); 49 | 50 | Console.Write("Password:"); 51 | o["password"] = Console.ReadLine(); 52 | 53 | Console.Write("E-mail address:"); 54 | o["address"] = Console.ReadLine(); 55 | 56 | Console.Write("Max size for each file(MiB):"); 57 | o["block"] = Console.ReadLine(); 58 | 59 | File.WriteAllText(GetBasePath() + "/mail.json", o.ToString()); 60 | 61 | Console.WriteLine("\r\ndone! enjoy!"); 62 | } 63 | 64 | /// 65 | /// return a VisualDisk 66 | /// 67 | /// VisualDisk 68 | public static VisualDisk GetDisk() 69 | { 70 | if (CheckSetings()) 71 | { 72 | string s = File.ReadAllText(GetBasePath() + "/mail.json"); 73 | JObject jo = (JObject)JsonConvert.DeserializeObject(s); 74 | var disk = new VisualDisk( 75 | (string)jo["imap"], 76 | (int)jo["port"], 77 | (bool)jo["ssl"], 78 | (string)jo["account"], 79 | (string)jo["password"], 80 | (string)jo["address"]); 81 | maxBlock = (long)jo["block"]; 82 | return disk; 83 | } 84 | else 85 | { 86 | Console.WriteLine("settings not found\r\npleause use -s command to set imap settings"); 87 | return null; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /maildisk/maildisk/apis/VisualDisk.cs: -------------------------------------------------------------------------------- 1 | using MailKit; 2 | using MailKit.Net.Imap; 3 | using MailKit.Net.Smtp; 4 | using MailKit.Search; 5 | using MimeKit; 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Text.RegularExpressions; 13 | using System.Threading; 14 | using System.Threading.Tasks; 15 | 16 | namespace maildisk.apis 17 | { 18 | class VisualDisk 19 | { 20 | private string address; 21 | private string account; 22 | private string password; 23 | private string imapServer; 24 | private int imapPort; 25 | private bool imapSsl; 26 | private string lastFolder; 27 | 28 | public VisualDisk(string imapServer, int port, bool useSsl, string account, string password, string address) 29 | { 30 | this.imapServer = imapServer; 31 | this.imapPort = port; 32 | this.imapSsl = useSsl; 33 | this.address = address; 34 | this.account = account; 35 | this.password = password; 36 | //auto mark as seen task 37 | Task.Run(() => { 38 | while(true) 39 | { 40 | try 41 | { 42 | if (lastFolder != null) 43 | { 44 | var client = GetImapClient(); 45 | var f = client.GetFolder(lastFolder); 46 | f.Open(FolderAccess.ReadWrite); 47 | var uids = f.Search(SearchQuery.NotSeen); 48 | foreach (var u in uids) 49 | { 50 | f.AddFlags(u, MessageFlags.Seen, true); 51 | 52 | Console.WriteLine($"[disk check]add a seen flag"); 53 | 54 | } 55 | client.Disconnect(true); 56 | } 57 | } 58 | catch(Exception e) 59 | { 60 | Console.WriteLine($"[disk check]fetch error: {e.Message}"); 61 | } 62 | Task.Delay(30 * 1000).Wait(); 63 | } 64 | }); 65 | } 66 | 67 | /// 68 | /// get a imap client 69 | /// 70 | /// imap client 71 | public ImapClient GetImapClient() 72 | { 73 | ImapClient client = new ImapClient(); 74 | client.ServerCertificateValidationCallback = (s, c, h, e) => true; 75 | client.Connect(imapServer, imapPort, imapSsl); 76 | client.Authenticate(account, password); 77 | //判断是否 添加ID COMMOND命令 78 | if ((client.Capabilities | ImapCapabilities.Id) == client.Capabilities) 79 | { 80 | var clientImplementation = new ImapImplementation 81 | { 82 | Name = "Foxmail", 83 | Version = "9.156" 84 | }; 85 | var serverImplementation = client.Identify(clientImplementation); 86 | } 87 | return client; 88 | } 89 | 90 | /// 91 | /// upload a file 92 | /// 93 | /// file name on cloud disk 94 | /// folder on email 95 | /// local file path 96 | /// file upload success or not 97 | private bool Upload(string fileName, string folderPath, Stream file) 98 | { 99 | 100 | Console.WriteLine($"[disk upload]upload {fileName} to mail folder {folderPath}, size:{file.Length}"); 101 | 102 | var client = GetImapClient(); 103 | var message = new MimeMessage(); 104 | message.From.Add(MailboxAddress.Parse(address)); 105 | message.To.Add(MailboxAddress.Parse(address)); 106 | message.Subject = "[mailDisk]" + fileName; 107 | var body = new TextPart("plain") 108 | { 109 | Text = 110 | "This mail was send by mail disk\r\n" + 111 | "please do not delete" 112 | }; 113 | 114 | var attachment = new MimePart() 115 | { 116 | Content = new MimeContent(file, ContentEncoding.Default), 117 | ContentDisposition = new ContentDisposition(ContentDisposition.Attachment), 118 | ContentTransferEncoding = ContentEncoding.Base64, 119 | FileName = "attachment.netdiskfile" 120 | }; 121 | 122 | var multipart = new Multipart("mixed"); 123 | multipart.Add(body); 124 | multipart.Add(attachment); 125 | message.Body = multipart; 126 | 127 | Console.WriteLine("[disk upload]appending..."); 128 | 129 | var folder = GetImapClient().GetFolder(folderPath); 130 | folder.Open(FolderAccess.ReadWrite); 131 | var uid = folder.Append(message); 132 | lastFolder = folderPath; 133 | 134 | 135 | Console.WriteLine($"[disk upload]upload success"); 136 | 137 | client.Disconnect(true); 138 | return true; 139 | } 140 | 141 | /// 142 | /// get file list with folder 143 | /// 144 | /// folder 145 | /// files' name 146 | public string[] GetFileList(string folderPath, bool deleteMissing = false) 147 | { 148 | ArrayList mails = new ArrayList(); 149 | var client = GetImapClient(); 150 | var folder = client.GetFolder(folderPath); 151 | folder.Open(FolderAccess.ReadWrite); 152 | Console.WriteLine($"find {folder.Count} mails in this folder"); 153 | for(int i = 0;i < folder.Count; i += 100) 154 | { 155 | Console.WriteLine($"fatching mails {i}/{folder.Count}"); 156 | int max = i + 100; 157 | if (max >= folder.Count) 158 | max = folder.Count - 1; 159 | foreach (var m in folder.Fetch(i, max, MessageSummaryItems.Full | MessageSummaryItems.UniqueId)) 160 | { 161 | if(m.Envelope.Subject.IndexOf("[mailDisk]") == 0) 162 | mails.Add(m.Envelope.Subject.Substring("[mailDisk]".Length)); 163 | } 164 | if (max == folder.Count) 165 | break; 166 | } 167 | Console.WriteLine($"mails in {folder.Count} fatched ok."); 168 | 169 | ArrayList files = new ArrayList(); 170 | foreach(string f in mails) 171 | { 172 | if (f.IndexOf("<")>=0) 173 | { 174 | MatchCollection mc = Regex.Matches(f, @"(.+?)<1/(\d+?)>"); 175 | if (mc.Count > 0 && mc[0].Groups.Count == 3) 176 | { 177 | Console.WriteLine($"find file {mc[0].Groups[1]} with {mc[0].Groups[2]} parts,checking..."); 178 | bool result = true;//check is it have all files 179 | int sum = int.Parse(mc[0].Groups[2].ToString()); 180 | for(int i=1;i<=sum;i++) 181 | { 182 | if(!mails.Contains($"{mc[0].Groups[1]}<{i}/{sum}>")) 183 | { 184 | result = false; 185 | break; 186 | } 187 | } 188 | if(result) 189 | { 190 | files.Add(mc[0].Groups[1].ToString()); 191 | Console.WriteLine($"file {mc[0].Groups[1]} check ok"); 192 | } 193 | else 194 | Console.WriteLine($"file {mc[0].Groups[1]}'s parts are missing"); 195 | } 196 | 197 | } 198 | else 199 | { 200 | files.Add(f); 201 | } 202 | } 203 | if(deleteMissing) 204 | { 205 | Console.WriteLine("start cleaning all missing mails"); 206 | for (int i = 0; i < folder.Count; i += 100) 207 | { 208 | Console.WriteLine($"fatching mails {i}/{folder.Count}"); 209 | int max = i + 100; 210 | if (max >= folder.Count) 211 | max = folder.Count - 1; 212 | foreach (var m in folder.Fetch(i, max, MessageSummaryItems.Full | MessageSummaryItems.UniqueId)) 213 | { 214 | if (m.Envelope.Subject.IndexOf("[mailDisk]") == 0) 215 | { 216 | string name = m.Envelope.Subject.Substring("[mailDisk]".Length); 217 | Console.WriteLine($"checking file {name}"); 218 | MatchCollection mc = Regex.Matches(name, @"(.+?)<(\d+?)/(\d+?)>"); 219 | if (mc.Count > 0 && mc[0].Groups.Count == 4) 220 | name = mc[0].Groups[1].ToString(); 221 | if (!files.Contains(name)) 222 | { 223 | Console.WriteLine($"file {name} is not right, mark as deleted"); 224 | folder.AddFlags(m.UniqueId, MessageFlags.Deleted, true); 225 | } 226 | } 227 | } 228 | if (max == folder.Count) 229 | break; 230 | } 231 | folder.Expunge(); 232 | } 233 | client.Disconnect(true); 234 | return (string[])files.ToArray(typeof(string)); 235 | } 236 | 237 | /// 238 | /// download file 239 | /// 240 | /// mail folder 241 | /// file save name 242 | public void DownloadFile(string folderPath, string fileName, string local, 243 | ImapClient client = null, 244 | IList all = null, 245 | IMailFolder folder = null) 246 | { 247 | if(File.Exists(local)) 248 | { 249 | Console.WriteLine($"error! file {local} already exist!"); 250 | return; 251 | } 252 | if(client == null) 253 | client = GetImapClient(); 254 | 255 | var invalidFileName = Path.GetInvalidFileNameChars(); 256 | fileName = invalidFileName.Aggregate(fileName, (o, r) => (o.Replace(r.ToString(), string.Empty))); 257 | 258 | if (all == null) 259 | { 260 | folder = client.GetFolder(folderPath); 261 | folder.Open(FolderAccess.ReadOnly); 262 | var uids = folder.Search(SearchQuery.SubjectContains($"[mailDisk]{fileName}")); 263 | 264 | Console.WriteLine($"find {uids.Count} matchs in this folder"); 265 | Console.WriteLine($"fatching mails"); 266 | all = folder.Fetch(uids, MessageSummaryItems.Full | MessageSummaryItems.UniqueId); 267 | } 268 | bool singleFile = true; 269 | int fileSum = 0; 270 | bool hasFile = false; 271 | foreach(var m in all) 272 | { 273 | string subject = m.Envelope.Subject.Substring("[mailDisk]".Length); 274 | if (subject.IndexOf(fileName) == 0 && (subject.Length == fileName.Length || subject.Substring(fileName.Length, 1) == "<")) 275 | { 276 | if(subject.Length == fileName.Length) 277 | { 278 | hasFile = true; 279 | break; 280 | } 281 | MatchCollection mc = Regex.Matches(subject, @"<1/(\d+?)>"); 282 | if (mc.Count > 0 && mc[0].Groups.Count == 2) 283 | { 284 | fileSum = int.Parse(mc[0].Groups[1].ToString()); 285 | singleFile = false; 286 | hasFile = true; 287 | break; 288 | } 289 | } 290 | } 291 | 292 | if(!hasFile) 293 | { 294 | Console.WriteLine($"error! file not exist!"); 295 | return; 296 | } 297 | 298 | if(singleFile) 299 | { 300 | foreach (var m in all) 301 | { 302 | if(m.Envelope.Subject.IndexOf($"[mailDisk]{fileName}") == 0) 303 | { 304 | while(true) 305 | { 306 | try 307 | { 308 | using (var output = File.Create(local)) 309 | { 310 | var r = Download(folder, m.UniqueId); 311 | r.Position = 0; 312 | r.CopyTo(output); 313 | break; 314 | } 315 | } 316 | catch(Exception e) 317 | { 318 | Console.WriteLine($"[disk Download]fail, retry, infomation:" + e.Message); 319 | } 320 | } 321 | } 322 | } 323 | } 324 | else 325 | { 326 | ArrayList mails = new ArrayList(); 327 | foreach (var m in all) 328 | { 329 | string subject = m.Envelope.Subject.Substring("[mailDisk]".Length); 330 | mails.Add(subject); 331 | } 332 | Console.WriteLine($"find file {fileName} with {fileSum} parts,checking..."); 333 | bool result = true;//check is it have all files 334 | for (int i = 1; i <= fileSum; i++) 335 | { 336 | if (!mails.Contains($"{fileName}<{i}/{fileSum}>")) 337 | { 338 | result = false; 339 | break; 340 | } 341 | } 342 | if (result) 343 | { 344 | Console.WriteLine($"file {fileName} check ok, begin download..."); 345 | using (var output = File.Create(local)) 346 | { 347 | for (int i = 1; i <= fileSum; i++) 348 | { 349 | foreach (var m in all) 350 | { 351 | if (m.Envelope.Subject.IndexOf($"[mailDisk]{fileName}<{i}/{fileSum}>") == 0) 352 | { 353 | while (true) 354 | { 355 | try 356 | { 357 | Console.WriteLine($"downloading {fileName}<{i}/{fileSum}> ..."); 358 | var r = Download(folder, m.UniqueId); 359 | r.Position = 0; 360 | r.CopyTo(output); 361 | break; 362 | } 363 | catch (Exception e) 364 | { 365 | Console.WriteLine($"[disk Download]fail, retry, infomation:" + e.Message); 366 | } 367 | } 368 | } 369 | } 370 | } 371 | } 372 | } 373 | else 374 | { 375 | Console.WriteLine($"file {fileName}'s parts are missing, download fail"); 376 | return; 377 | } 378 | } 379 | Console.WriteLine($"file {fileName} download success!"); 380 | } 381 | 382 | /// 383 | /// download a mail's attachments 384 | /// 385 | /// file folder 386 | /// mail uid 387 | /// file's stream 388 | private Stream Download(IMailFolder folder, UniqueId id) 389 | { 390 | Stream stream = new MemoryStream(); 391 | MimeMessage message = folder.GetMessage(id); 392 | foreach (MimePart attachment in message.Attachments) 393 | { 394 | //下载附件 395 | using (var cancel = new CancellationTokenSource()) 396 | { 397 | attachment.Content.DecodeTo(stream, cancel.Token); 398 | return stream; 399 | } 400 | } 401 | return stream; 402 | } 403 | 404 | 405 | 406 | /// 407 | /// upload big files, auto split by setting's size 408 | /// 409 | /// file name on cloud disk 410 | /// folder on email 411 | /// local file path 412 | /// max size for each mail 413 | /// file upload success or not 414 | public bool UploadBigFile(string fileName, string folderPath, string filePath, int blockSize) 415 | { 416 | if (!File.Exists(filePath)) 417 | { 418 | Console.WriteLine($"error! file {filePath} not exist!"); 419 | return false; 420 | } 421 | fileName = fileName.Replace("\\", "/"); 422 | while(fileName.IndexOf("/") == 0)//remove the "/" head 423 | { 424 | fileName = fileName.Substring(1); 425 | } 426 | 427 | FileInfo fileInfo = new FileInfo(filePath); 428 | if (fileInfo.Length > blockSize) 429 | { 430 | 431 | Console.WriteLine($"[disk Upload]file need to be splited"); 432 | 433 | var steps = (int)Math.Ceiling((double)fileInfo.Length / blockSize); 434 | using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 435 | { 436 | using (BinaryReader br = new BinaryReader(fs)) 437 | { 438 | int couter = 1; 439 | bool isReadingComplete = false; 440 | while (!isReadingComplete) 441 | { 442 | string tempName = couter.ToString(); 443 | 444 | byte[] bytes = br.ReadBytes(blockSize); 445 | Stream stream = new MemoryStream(bytes); 446 | 447 | bool result = false; 448 | while(!result) 449 | { 450 | try 451 | { 452 | result = Upload(fileName + $"<{couter}/{steps}>", folderPath, stream); 453 | } 454 | catch(Exception e) 455 | { 456 | Console.WriteLine($"[disk Upload]fail, retry, infomation:" + e.Message); 457 | } 458 | } 459 | 460 | isReadingComplete = (bytes.Length != blockSize); 461 | if (!isReadingComplete) 462 | { 463 | couter += 1; 464 | } 465 | } 466 | } 467 | } 468 | return true; 469 | } 470 | else 471 | { 472 | bool result = false; 473 | while (!result) 474 | { 475 | try 476 | { 477 | result = Upload(fileName, folderPath, File.OpenRead(filePath)); 478 | } 479 | catch (Exception e) 480 | { 481 | Console.WriteLine($"[disk Upload]fail, retry, infomation:" + e.Message); 482 | } 483 | } 484 | return true; 485 | } 486 | } 487 | 488 | 489 | /// 490 | /// get all folders in this mail 491 | /// 492 | /// path, default is empty 493 | /// folder list 494 | public IMailFolder[] GetFolders(string path = "") 495 | { 496 | ArrayList folders = new ArrayList(); 497 | 498 | var personal = GetImapClient().GetFolder(path); 499 | 500 | foreach (var folder in personal.GetSubfolders(false)) 501 | { 502 | if (folder.GetSubfolders(false).Count > 0) 503 | { 504 | folders.Add(folder); 505 | folders.AddRange(GetFolders(folder.FullName)); 506 | } 507 | else 508 | { 509 | folders.Add(folder); 510 | } 511 | } 512 | return (IMailFolder[])folders.ToArray(typeof(IMailFolder)); 513 | } 514 | 515 | 516 | /// 517 | /// create a folder 518 | /// 519 | /// folder name and path 520 | /// result 521 | public void CreatFolder(string path) 522 | { 523 | var client = GetImapClient(); 524 | if(path.IndexOf("/") < 0) 525 | { 526 | var root = client.GetFolder(""); 527 | root.Create(path, true); 528 | } 529 | else 530 | { 531 | int l = path.LastIndexOf("/"); 532 | var folder = client.GetFolder(path.Substring(0,l)); 533 | folder.Create(path.Substring(l+1), true); 534 | } 535 | Console.WriteLine($"[disk folder]folder {path} is created."); 536 | } 537 | 538 | private ArrayList tempFiles = new ArrayList(); 539 | public void RefreshFiles(string folderPath) 540 | { 541 | tempFiles.AddRange(GetFileList(folderPath)); 542 | foreach (var s in tempFiles) 543 | { 544 | Console.WriteLine(s); 545 | } 546 | } 547 | 548 | /// 549 | /// upload a folder 550 | /// 551 | /// cloud folder path 552 | /// mail folder 553 | /// local folder path 554 | /// each mail size 555 | public void UploadFolder(string cloudPath, string folderPath, string localPath, int blockSize) 556 | { 557 | while (cloudPath.LastIndexOf("/") == cloudPath.Length - 1)//remove last "/" 558 | { 559 | cloudPath = cloudPath.Substring(0, cloudPath.Length - 1); 560 | } 561 | Console.WriteLine($"[disk upload folder]upload {localPath} to {cloudPath}"); 562 | if (!Directory.Exists(localPath)) 563 | { 564 | Console.WriteLine($"error! folder {localPath} not exist!"); 565 | return; 566 | } 567 | 568 | foreach (var f in Directory.GetDirectories(localPath)) 569 | { 570 | int l = f.LastIndexOf("/"); 571 | if(l == -1) 572 | l = f.LastIndexOf("\\"); 573 | UploadFolder(cloudPath + f.Substring(l), folderPath, f, blockSize); 574 | } 575 | foreach(var f in Directory.GetFiles(localPath)) 576 | { 577 | int l = f.LastIndexOf("/"); 578 | if (l == -1) 579 | l = f.LastIndexOf("\\"); 580 | 581 | string fileName = cloudPath + f.Substring(l); 582 | fileName = fileName.Replace("\\", "/"); 583 | while (fileName.IndexOf("/") == 0)//remove the "/" head 584 | { 585 | fileName = fileName.Substring(1); 586 | } 587 | 588 | if (tempFiles.Contains(fileName)) 589 | Console.WriteLine($"[disk upload folder] file {fileName} is" + 590 | $" already exist in mail disk, skip upload."); 591 | else 592 | UploadBigFile(fileName, folderPath, f, blockSize); 593 | } 594 | } 595 | 596 | /// 597 | /// download a floder 598 | /// 599 | /// cloud folder path 600 | /// mail folder 601 | /// local folder path 602 | public void DownloadFolder(string cloudPath, string folderPath, string localPath) 603 | { 604 | while (cloudPath.LastIndexOf("/") == cloudPath.Length - 1)//remove last "/" 605 | { 606 | cloudPath = cloudPath.Substring(0, cloudPath.Length - 1); 607 | } 608 | cloudPath += "/"; 609 | localPath = localPath.Replace("\\", "/"); 610 | if (localPath.LastIndexOf("/") != localPath.Length - 1) 611 | localPath += "/"; 612 | Console.WriteLine($"[disk download folder]download {cloudPath} to {localPath}"); 613 | 614 | var client = GetImapClient(); 615 | var folder = client.GetFolder(folderPath); 616 | folder.Open(FolderAccess.ReadOnly); 617 | 618 | Console.WriteLine($"find {folder.Count} files in this folder"); 619 | Console.WriteLine($"fatching mails"); 620 | var all = folder.Fetch(0,-1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId); 621 | 622 | foreach (string f in GetFileList(folderPath)) 623 | { 624 | if(f.IndexOf(cloudPath) == 0)//match folder 625 | { 626 | string localFile = localPath + f.Substring(cloudPath.Length); 627 | localPath = localPath.Replace("\\", "/"); 628 | int l = localFile.LastIndexOf("/"); 629 | Directory.CreateDirectory(localFile.Substring(0, l)); 630 | DownloadFile(folderPath, f, localFile, client, all, folder); 631 | } 632 | } 633 | } 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /maildisk/maildisk/maildisk.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | chenxuuu 7 | chenxublog.com 8 | make your e-mail become a netdisk! 9 | 1.0.2.3 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /pictures/big.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuuu/Mail-Box-Net-Disk/dcb1222d01c5596fd6073898f37650a31f35292b/pictures/big.psd -------------------------------------------------------------------------------- /pictures/mail.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuuu/Mail-Box-Net-Disk/dcb1222d01c5596fd6073898f37650a31f35292b/pictures/mail.psd -------------------------------------------------------------------------------- /pictures/mailico.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenxuuu/Mail-Box-Net-Disk/dcb1222d01c5596fd6073898f37650a31f35292b/pictures/mailico.psd --------------------------------------------------------------------------------