├── Cloudreve_FileSynchronizer.csproj ├── Cloudreve_FileSynchronizer.sln ├── EF ├── CloudreveContext.cs ├── Downloads.cs ├── Files.cs ├── Folders.cs ├── Groups.cs ├── Policies.cs ├── Settings.cs ├── Shares.cs ├── Tags.cs ├── Tasks.cs ├── Users.cs └── Webdavs.cs ├── LICENSE ├── Program.cs ├── ProgramParameter.cs ├── Properties └── launchSettings.json └── README.md /Cloudreve_FileSynchronizer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Cloudreve_FileSynchronizer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29806.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cloudreve_FileSynchronizer", "Cloudreve_FileSynchronizer.csproj", "{199BE4B1-CE9D-4616-9953-379CE99FCADC}" 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 | {199BE4B1-CE9D-4616-9953-379CE99FCADC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {199BE4B1-CE9D-4616-9953-379CE99FCADC}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {199BE4B1-CE9D-4616-9953-379CE99FCADC}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {199BE4B1-CE9D-4616-9953-379CE99FCADC}.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 = {8B752CE3-F0AC-42A6-B386-3D2B816B4D42} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /EF/CloudreveContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata; 4 | 5 | namespace Cloudreve_FileSynchronizer.EF 6 | { 7 | public partial class CloudreveContext : DbContext 8 | { 9 | private string ConnectionString { get; set; } 10 | 11 | public CloudreveContext(string connectionString) 12 | { 13 | this.ConnectionString = connectionString; 14 | } 15 | 16 | public CloudreveContext(DbContextOptions options, string connectionString) 17 | : base(options) 18 | { 19 | this.ConnectionString = connectionString; 20 | } 21 | 22 | public virtual DbSet Downloads { get; set; } 23 | public virtual DbSet Files { get; set; } 24 | public virtual DbSet Folders { get; set; } 25 | public virtual DbSet Groups { get; set; } 26 | public virtual DbSet Policies { get; set; } 27 | public virtual DbSet Settings { get; set; } 28 | public virtual DbSet Shares { get; set; } 29 | public virtual DbSet Tags { get; set; } 30 | public virtual DbSet Tasks { get; set; } 31 | public virtual DbSet Users { get; set; } 32 | public virtual DbSet Webdavs { get; set; } 33 | 34 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 35 | { 36 | if (!optionsBuilder.IsConfigured) 37 | optionsBuilder.UseMySql(ConnectionString, x => x.ServerVersion("5.7.27-mysql")); 38 | } 39 | 40 | protected override void OnModelCreating(ModelBuilder modelBuilder) 41 | { 42 | modelBuilder.Entity(entity => 43 | { 44 | entity.ToTable("downloads"); 45 | 46 | entity.HasIndex(e => e.DeletedAt) 47 | .HasName("idx_downloads_deleted_at"); 48 | 49 | entity.Property(e => e.Id) 50 | .HasColumnName("id") 51 | .HasColumnType("int(10) unsigned"); 52 | 53 | entity.Property(e => e.Attrs) 54 | .HasColumnName("attrs") 55 | .HasColumnType("text") 56 | .HasCharSet("utf8mb4") 57 | .HasCollation("utf8mb4_bin"); 58 | 59 | entity.Property(e => e.CreatedAt) 60 | .HasColumnName("created_at") 61 | .HasColumnType("timestamp"); 62 | 63 | entity.Property(e => e.DeletedAt) 64 | .HasColumnName("deleted_at") 65 | .HasColumnType("timestamp"); 66 | 67 | entity.Property(e => e.DownloadedSize) 68 | .HasColumnName("downloaded_size") 69 | .HasColumnType("bigint(20) unsigned"); 70 | 71 | entity.Property(e => e.Dst) 72 | .HasColumnName("dst") 73 | .HasColumnType("text") 74 | .HasCharSet("utf8mb4") 75 | .HasCollation("utf8mb4_bin"); 76 | 77 | entity.Property(e => e.Error) 78 | .HasColumnName("error") 79 | .HasColumnType("text") 80 | .HasCharSet("utf8mb4") 81 | .HasCollation("utf8mb4_bin"); 82 | 83 | entity.Property(e => e.GId) 84 | .HasColumnName("g_id") 85 | .HasColumnType("longtext") 86 | .HasCharSet("utf8mb4") 87 | .HasCollation("utf8mb4_bin"); 88 | 89 | entity.Property(e => e.Parent) 90 | .HasColumnName("parent") 91 | .HasColumnType("text") 92 | .HasCharSet("utf8mb4") 93 | .HasCollation("utf8mb4_bin"); 94 | 95 | entity.Property(e => e.Source) 96 | .HasColumnName("source") 97 | .HasColumnType("text") 98 | .HasCharSet("utf8mb4") 99 | .HasCollation("utf8mb4_bin"); 100 | 101 | entity.Property(e => e.Speed) 102 | .HasColumnName("speed") 103 | .HasColumnType("int(11)"); 104 | 105 | entity.Property(e => e.Status) 106 | .HasColumnName("status") 107 | .HasColumnType("int(11)"); 108 | 109 | entity.Property(e => e.TaskId) 110 | .HasColumnName("task_id") 111 | .HasColumnType("int(10) unsigned"); 112 | 113 | entity.Property(e => e.TotalSize) 114 | .HasColumnName("total_size") 115 | .HasColumnType("bigint(20) unsigned"); 116 | 117 | entity.Property(e => e.Type) 118 | .HasColumnName("type") 119 | .HasColumnType("int(11)"); 120 | 121 | entity.Property(e => e.UpdatedAt) 122 | .HasColumnName("updated_at") 123 | .HasColumnType("timestamp"); 124 | 125 | entity.Property(e => e.UserId) 126 | .HasColumnName("user_id") 127 | .HasColumnType("int(10) unsigned"); 128 | }); 129 | 130 | modelBuilder.Entity(entity => 131 | { 132 | entity.ToTable("files"); 133 | 134 | entity.HasIndex(e => e.DeletedAt) 135 | .HasName("idx_files_deleted_at"); 136 | 137 | entity.HasIndex(e => e.FolderId) 138 | .HasName("folder_id"); 139 | 140 | entity.HasIndex(e => e.UserId) 141 | .HasName("user_id"); 142 | 143 | entity.HasIndex(e => new { e.Name, e.UserId, e.FolderId }) 144 | .HasName("idx_only_one") 145 | .IsUnique(); 146 | 147 | entity.Property(e => e.Id) 148 | .HasColumnName("id") 149 | .HasColumnType("int(10) unsigned"); 150 | 151 | entity.Property(e => e.CreatedAt) 152 | .HasColumnName("created_at") 153 | .HasColumnType("timestamp"); 154 | 155 | entity.Property(e => e.DeletedAt) 156 | .HasColumnName("deleted_at") 157 | .HasColumnType("timestamp"); 158 | 159 | entity.Property(e => e.FolderId) 160 | .HasColumnName("folder_id") 161 | .HasColumnType("int(10) unsigned"); 162 | 163 | entity.Property(e => e.Name) 164 | .HasColumnName("name") 165 | .HasColumnType("varchar(255)") 166 | .HasCharSet("utf8mb4") 167 | .HasCollation("utf8mb4_bin"); 168 | 169 | entity.Property(e => e.PicInfo) 170 | .HasColumnName("pic_info") 171 | .HasColumnType("varchar(255)") 172 | .HasCharSet("utf8mb4") 173 | .HasCollation("utf8mb4_bin"); 174 | 175 | entity.Property(e => e.PolicyId) 176 | .HasColumnName("policy_id") 177 | .HasColumnType("int(10) unsigned"); 178 | 179 | entity.Property(e => e.Size) 180 | .HasColumnName("size") 181 | .HasColumnType("bigint(20) unsigned"); 182 | 183 | entity.Property(e => e.SourceName) 184 | .HasColumnName("source_name") 185 | .HasColumnType("text") 186 | .HasCharSet("utf8mb4") 187 | .HasCollation("utf8mb4_bin"); 188 | 189 | entity.Property(e => e.UpdatedAt) 190 | .HasColumnName("updated_at") 191 | .HasColumnType("timestamp"); 192 | 193 | entity.Property(e => e.UserId) 194 | .HasColumnName("user_id") 195 | .HasColumnType("int(10) unsigned"); 196 | }); 197 | 198 | modelBuilder.Entity(entity => 199 | { 200 | entity.ToTable("folders"); 201 | 202 | entity.HasIndex(e => e.DeletedAt) 203 | .HasName("idx_folders_deleted_at"); 204 | 205 | entity.HasIndex(e => e.OwnerId) 206 | .HasName("owner_id"); 207 | 208 | entity.HasIndex(e => e.ParentId) 209 | .HasName("parent_id"); 210 | 211 | entity.HasIndex(e => new { e.Name, e.ParentId }) 212 | .HasName("idx_only_one_name") 213 | .IsUnique(); 214 | 215 | entity.Property(e => e.Id) 216 | .HasColumnName("id") 217 | .HasColumnType("int(10) unsigned"); 218 | 219 | entity.Property(e => e.CreatedAt) 220 | .HasColumnName("created_at") 221 | .HasColumnType("timestamp"); 222 | 223 | entity.Property(e => e.DeletedAt) 224 | .HasColumnName("deleted_at") 225 | .HasColumnType("timestamp"); 226 | 227 | entity.Property(e => e.Name) 228 | .HasColumnName("name") 229 | .HasColumnType("varchar(255)") 230 | .HasCharSet("utf8mb4") 231 | .HasCollation("utf8mb4_bin"); 232 | 233 | entity.Property(e => e.OwnerId) 234 | .HasColumnName("owner_id") 235 | .HasColumnType("int(10) unsigned"); 236 | 237 | entity.Property(e => e.ParentId) 238 | .HasColumnName("parent_id") 239 | .HasColumnType("int(10) unsigned"); 240 | 241 | entity.Property(e => e.UpdatedAt) 242 | .HasColumnName("updated_at") 243 | .HasColumnType("timestamp"); 244 | }); 245 | 246 | modelBuilder.Entity(entity => 247 | { 248 | entity.ToTable("groups"); 249 | 250 | entity.HasIndex(e => e.DeletedAt) 251 | .HasName("idx_groups_deleted_at"); 252 | 253 | entity.Property(e => e.Id) 254 | .HasColumnName("id") 255 | .HasColumnType("int(10) unsigned"); 256 | 257 | entity.Property(e => e.CreatedAt) 258 | .HasColumnName("created_at") 259 | .HasColumnType("timestamp"); 260 | 261 | entity.Property(e => e.DeletedAt) 262 | .HasColumnName("deleted_at") 263 | .HasColumnType("timestamp"); 264 | 265 | entity.Property(e => e.MaxStorage) 266 | .HasColumnName("max_storage") 267 | .HasColumnType("bigint(20) unsigned"); 268 | 269 | entity.Property(e => e.Name) 270 | .HasColumnName("name") 271 | .HasColumnType("varchar(255)") 272 | .HasCharSet("utf8mb4") 273 | .HasCollation("utf8mb4_bin"); 274 | 275 | entity.Property(e => e.Options) 276 | .HasColumnName("options") 277 | .HasColumnType("varchar(255)") 278 | .HasCharSet("utf8mb4") 279 | .HasCollation("utf8mb4_bin"); 280 | 281 | entity.Property(e => e.Policies) 282 | .HasColumnName("policies") 283 | .HasColumnType("varchar(255)") 284 | .HasCharSet("utf8mb4") 285 | .HasCollation("utf8mb4_bin"); 286 | 287 | entity.Property(e => e.ShareEnabled).HasColumnName("share_enabled"); 288 | 289 | entity.Property(e => e.SpeedLimit) 290 | .HasColumnName("speed_limit") 291 | .HasColumnType("int(11)"); 292 | 293 | entity.Property(e => e.UpdatedAt) 294 | .HasColumnName("updated_at") 295 | .HasColumnType("timestamp"); 296 | 297 | entity.Property(e => e.WebDavEnabled).HasColumnName("web_dav_enabled"); 298 | }); 299 | 300 | modelBuilder.Entity(entity => 301 | { 302 | entity.ToTable("policies"); 303 | 304 | entity.HasIndex(e => e.DeletedAt) 305 | .HasName("idx_policies_deleted_at"); 306 | 307 | entity.Property(e => e.Id) 308 | .HasColumnName("id") 309 | .HasColumnType("int(10) unsigned"); 310 | 311 | entity.Property(e => e.AccessKey) 312 | .HasColumnName("access_key") 313 | .HasColumnType("text") 314 | .HasCharSet("utf8mb4") 315 | .HasCollation("utf8mb4_bin"); 316 | 317 | entity.Property(e => e.AutoRename).HasColumnName("auto_rename"); 318 | 319 | entity.Property(e => e.BaseUrl) 320 | .HasColumnName("base_url") 321 | .HasColumnType("varchar(255)") 322 | .HasCharSet("utf8mb4") 323 | .HasCollation("utf8mb4_bin"); 324 | 325 | entity.Property(e => e.BucketName) 326 | .HasColumnName("bucket_name") 327 | .HasColumnType("varchar(255)") 328 | .HasCharSet("utf8mb4") 329 | .HasCollation("utf8mb4_bin"); 330 | 331 | entity.Property(e => e.CreatedAt) 332 | .HasColumnName("created_at") 333 | .HasColumnType("timestamp"); 334 | 335 | entity.Property(e => e.DeletedAt) 336 | .HasColumnName("deleted_at") 337 | .HasColumnType("timestamp"); 338 | 339 | entity.Property(e => e.DirNameRule) 340 | .HasColumnName("dir_name_rule") 341 | .HasColumnType("varchar(255)") 342 | .HasCharSet("utf8mb4") 343 | .HasCollation("utf8mb4_bin"); 344 | 345 | entity.Property(e => e.FileNameRule) 346 | .HasColumnName("file_name_rule") 347 | .HasColumnType("varchar(255)") 348 | .HasCharSet("utf8mb4") 349 | .HasCollation("utf8mb4_bin"); 350 | 351 | entity.Property(e => e.IsOriginLinkEnable).HasColumnName("is_origin_link_enable"); 352 | 353 | entity.Property(e => e.IsPrivate).HasColumnName("is_private"); 354 | 355 | entity.Property(e => e.MaxSize) 356 | .HasColumnName("max_size") 357 | .HasColumnType("bigint(20) unsigned"); 358 | 359 | entity.Property(e => e.Name) 360 | .HasColumnName("name") 361 | .HasColumnType("varchar(255)") 362 | .HasCharSet("utf8mb4") 363 | .HasCollation("utf8mb4_bin"); 364 | 365 | entity.Property(e => e.Options) 366 | .HasColumnName("options") 367 | .HasColumnType("text") 368 | .HasCharSet("utf8mb4") 369 | .HasCollation("utf8mb4_bin"); 370 | 371 | entity.Property(e => e.SecretKey) 372 | .HasColumnName("secret_key") 373 | .HasColumnType("text") 374 | .HasCharSet("utf8mb4") 375 | .HasCollation("utf8mb4_bin"); 376 | 377 | entity.Property(e => e.Server) 378 | .HasColumnName("server") 379 | .HasColumnType("varchar(255)") 380 | .HasCharSet("utf8mb4") 381 | .HasCollation("utf8mb4_bin"); 382 | 383 | entity.Property(e => e.Type) 384 | .HasColumnName("type") 385 | .HasColumnType("varchar(255)") 386 | .HasCharSet("utf8mb4") 387 | .HasCollation("utf8mb4_bin"); 388 | 389 | entity.Property(e => e.UpdatedAt) 390 | .HasColumnName("updated_at") 391 | .HasColumnType("timestamp"); 392 | }); 393 | 394 | modelBuilder.Entity(entity => 395 | { 396 | entity.ToTable("settings"); 397 | 398 | entity.HasIndex(e => e.DeletedAt) 399 | .HasName("idx_settings_deleted_at"); 400 | 401 | entity.HasIndex(e => e.Name) 402 | .HasName("setting_key"); 403 | 404 | entity.Property(e => e.Id) 405 | .HasColumnName("id") 406 | .HasColumnType("int(10) unsigned"); 407 | 408 | entity.Property(e => e.CreatedAt) 409 | .HasColumnName("created_at") 410 | .HasColumnType("timestamp"); 411 | 412 | entity.Property(e => e.DeletedAt) 413 | .HasColumnName("deleted_at") 414 | .HasColumnType("timestamp"); 415 | 416 | entity.Property(e => e.Name) 417 | .IsRequired() 418 | .HasColumnName("name") 419 | .HasColumnType("varchar(255)") 420 | .HasCharSet("utf8mb4") 421 | .HasCollation("utf8mb4_bin"); 422 | 423 | entity.Property(e => e.Type) 424 | .IsRequired() 425 | .HasColumnName("type") 426 | .HasColumnType("varchar(255)") 427 | .HasCharSet("utf8mb4") 428 | .HasCollation("utf8mb4_bin"); 429 | 430 | entity.Property(e => e.UpdatedAt) 431 | .HasColumnName("updated_at") 432 | .HasColumnType("timestamp"); 433 | 434 | entity.Property(e => e.Value) 435 | .HasColumnName("value") 436 | .HasColumnType("longtext") 437 | .HasCharSet("utf8mb4") 438 | .HasCollation("utf8mb4_bin"); 439 | }); 440 | 441 | modelBuilder.Entity(entity => 442 | { 443 | entity.ToTable("shares"); 444 | 445 | entity.HasIndex(e => e.DeletedAt) 446 | .HasName("idx_shares_deleted_at"); 447 | 448 | entity.HasIndex(e => e.SourceName) 449 | .HasName("source"); 450 | 451 | entity.Property(e => e.Id) 452 | .HasColumnName("id") 453 | .HasColumnType("int(10) unsigned"); 454 | 455 | entity.Property(e => e.CreatedAt) 456 | .HasColumnName("created_at") 457 | .HasColumnType("timestamp"); 458 | 459 | entity.Property(e => e.DeletedAt) 460 | .HasColumnName("deleted_at") 461 | .HasColumnType("timestamp"); 462 | 463 | entity.Property(e => e.Downloads) 464 | .HasColumnName("downloads") 465 | .HasColumnType("int(11)"); 466 | 467 | entity.Property(e => e.Expires) 468 | .HasColumnName("expires") 469 | .HasColumnType("timestamp"); 470 | 471 | entity.Property(e => e.IsDir).HasColumnName("is_dir"); 472 | 473 | entity.Property(e => e.Password) 474 | .HasColumnName("password") 475 | .HasColumnType("varchar(255)") 476 | .HasCharSet("utf8mb4") 477 | .HasCollation("utf8mb4_bin"); 478 | 479 | entity.Property(e => e.PreviewEnabled).HasColumnName("preview_enabled"); 480 | 481 | entity.Property(e => e.RemainDownloads) 482 | .HasColumnName("remain_downloads") 483 | .HasColumnType("int(11)"); 484 | 485 | entity.Property(e => e.SourceId) 486 | .HasColumnName("source_id") 487 | .HasColumnType("int(10) unsigned"); 488 | 489 | entity.Property(e => e.SourceName) 490 | .HasColumnName("source_name") 491 | .HasColumnType("varchar(255)") 492 | .HasCharSet("utf8mb4") 493 | .HasCollation("utf8mb4_bin"); 494 | 495 | entity.Property(e => e.UpdatedAt) 496 | .HasColumnName("updated_at") 497 | .HasColumnType("timestamp"); 498 | 499 | entity.Property(e => e.UserId) 500 | .HasColumnName("user_id") 501 | .HasColumnType("int(10) unsigned"); 502 | 503 | entity.Property(e => e.Views) 504 | .HasColumnName("views") 505 | .HasColumnType("int(11)"); 506 | }); 507 | 508 | modelBuilder.Entity(entity => 509 | { 510 | entity.ToTable("tags"); 511 | 512 | entity.HasIndex(e => e.DeletedAt) 513 | .HasName("idx_tags_deleted_at"); 514 | 515 | entity.Property(e => e.Id) 516 | .HasColumnName("id") 517 | .HasColumnType("int(10) unsigned"); 518 | 519 | entity.Property(e => e.Color) 520 | .HasColumnName("color") 521 | .HasColumnType("varchar(255)") 522 | .HasCharSet("utf8mb4") 523 | .HasCollation("utf8mb4_bin"); 524 | 525 | entity.Property(e => e.CreatedAt) 526 | .HasColumnName("created_at") 527 | .HasColumnType("timestamp"); 528 | 529 | entity.Property(e => e.DeletedAt) 530 | .HasColumnName("deleted_at") 531 | .HasColumnType("timestamp"); 532 | 533 | entity.Property(e => e.Expression) 534 | .HasColumnName("expression") 535 | .HasColumnType("text") 536 | .HasCharSet("utf8mb4") 537 | .HasCollation("utf8mb4_bin"); 538 | 539 | entity.Property(e => e.Icon) 540 | .HasColumnName("icon") 541 | .HasColumnType("varchar(255)") 542 | .HasCharSet("utf8mb4") 543 | .HasCollation("utf8mb4_bin"); 544 | 545 | entity.Property(e => e.Name) 546 | .HasColumnName("name") 547 | .HasColumnType("varchar(255)") 548 | .HasCharSet("utf8mb4") 549 | .HasCollation("utf8mb4_bin"); 550 | 551 | entity.Property(e => e.Type) 552 | .HasColumnName("type") 553 | .HasColumnType("int(11)"); 554 | 555 | entity.Property(e => e.UpdatedAt) 556 | .HasColumnName("updated_at") 557 | .HasColumnType("timestamp"); 558 | 559 | entity.Property(e => e.UserId) 560 | .HasColumnName("user_id") 561 | .HasColumnType("int(10) unsigned"); 562 | }); 563 | 564 | modelBuilder.Entity(entity => 565 | { 566 | entity.ToTable("tasks"); 567 | 568 | entity.HasIndex(e => e.DeletedAt) 569 | .HasName("idx_tasks_deleted_at"); 570 | 571 | entity.Property(e => e.Id) 572 | .HasColumnName("id") 573 | .HasColumnType("int(10) unsigned"); 574 | 575 | entity.Property(e => e.CreatedAt) 576 | .HasColumnName("created_at") 577 | .HasColumnType("timestamp"); 578 | 579 | entity.Property(e => e.DeletedAt) 580 | .HasColumnName("deleted_at") 581 | .HasColumnType("timestamp"); 582 | 583 | entity.Property(e => e.Error) 584 | .HasColumnName("error") 585 | .HasColumnType("text") 586 | .HasCharSet("utf8mb4") 587 | .HasCollation("utf8mb4_bin"); 588 | 589 | entity.Property(e => e.Progress) 590 | .HasColumnName("progress") 591 | .HasColumnType("int(11)"); 592 | 593 | entity.Property(e => e.Props) 594 | .HasColumnName("props") 595 | .HasColumnType("text") 596 | .HasCharSet("utf8mb4") 597 | .HasCollation("utf8mb4_bin"); 598 | 599 | entity.Property(e => e.Status) 600 | .HasColumnName("status") 601 | .HasColumnType("int(11)"); 602 | 603 | entity.Property(e => e.Type) 604 | .HasColumnName("type") 605 | .HasColumnType("int(11)"); 606 | 607 | entity.Property(e => e.UpdatedAt) 608 | .HasColumnName("updated_at") 609 | .HasColumnType("timestamp"); 610 | 611 | entity.Property(e => e.UserId) 612 | .HasColumnName("user_id") 613 | .HasColumnType("int(10) unsigned"); 614 | }); 615 | 616 | modelBuilder.Entity(entity => 617 | { 618 | entity.ToTable("users"); 619 | 620 | entity.HasIndex(e => e.DeletedAt) 621 | .HasName("idx_users_deleted_at"); 622 | 623 | entity.HasIndex(e => e.Email) 624 | .HasName("uix_users_email") 625 | .IsUnique(); 626 | 627 | entity.Property(e => e.Id) 628 | .HasColumnName("id") 629 | .HasColumnType("int(10) unsigned"); 630 | 631 | entity.Property(e => e.Authn) 632 | .HasColumnName("authn") 633 | .HasColumnType("text") 634 | .HasCharSet("utf8mb4") 635 | .HasCollation("utf8mb4_bin"); 636 | 637 | entity.Property(e => e.Avatar) 638 | .HasColumnName("avatar") 639 | .HasColumnType("varchar(255)") 640 | .HasCharSet("utf8mb4") 641 | .HasCollation("utf8mb4_bin"); 642 | 643 | entity.Property(e => e.CreatedAt) 644 | .HasColumnName("created_at") 645 | .HasColumnType("timestamp"); 646 | 647 | entity.Property(e => e.DeletedAt) 648 | .HasColumnName("deleted_at") 649 | .HasColumnType("timestamp"); 650 | 651 | entity.Property(e => e.Email) 652 | .HasColumnName("email") 653 | .HasColumnType("varchar(100)") 654 | .HasCharSet("utf8mb4") 655 | .HasCollation("utf8mb4_bin"); 656 | 657 | entity.Property(e => e.GroupId) 658 | .HasColumnName("group_id") 659 | .HasColumnType("int(10) unsigned"); 660 | 661 | entity.Property(e => e.Nick) 662 | .HasColumnName("nick") 663 | .HasColumnType("varchar(50)") 664 | .HasCharSet("utf8mb4") 665 | .HasCollation("utf8mb4_bin"); 666 | 667 | entity.Property(e => e.Options) 668 | .HasColumnName("options") 669 | .HasColumnType("varchar(255)") 670 | .HasCharSet("utf8mb4") 671 | .HasCollation("utf8mb4_bin"); 672 | 673 | entity.Property(e => e.Password) 674 | .HasColumnName("password") 675 | .HasColumnType("varchar(255)") 676 | .HasCharSet("utf8mb4") 677 | .HasCollation("utf8mb4_bin"); 678 | 679 | entity.Property(e => e.Status) 680 | .HasColumnName("status") 681 | .HasColumnType("int(11)"); 682 | 683 | entity.Property(e => e.Storage) 684 | .HasColumnName("storage") 685 | .HasColumnType("bigint(20) unsigned"); 686 | 687 | entity.Property(e => e.TwoFactor) 688 | .HasColumnName("two_factor") 689 | .HasColumnType("varchar(255)") 690 | .HasCharSet("utf8mb4") 691 | .HasCollation("utf8mb4_bin"); 692 | 693 | entity.Property(e => e.UpdatedAt) 694 | .HasColumnName("updated_at") 695 | .HasColumnType("timestamp"); 696 | }); 697 | 698 | modelBuilder.Entity(entity => 699 | { 700 | entity.ToTable("webdavs"); 701 | 702 | entity.HasIndex(e => e.DeletedAt) 703 | .HasName("idx_webdavs_deleted_at"); 704 | 705 | entity.HasIndex(e => new { e.Password, e.UserId }) 706 | .HasName("password_only_on") 707 | .IsUnique(); 708 | 709 | entity.Property(e => e.Id) 710 | .HasColumnName("id") 711 | .HasColumnType("int(10) unsigned"); 712 | 713 | entity.Property(e => e.CreatedAt) 714 | .HasColumnName("created_at") 715 | .HasColumnType("timestamp"); 716 | 717 | entity.Property(e => e.DeletedAt) 718 | .HasColumnName("deleted_at") 719 | .HasColumnType("timestamp"); 720 | 721 | entity.Property(e => e.Name) 722 | .HasColumnName("name") 723 | .HasColumnType("varchar(255)") 724 | .HasCharSet("utf8mb4") 725 | .HasCollation("utf8mb4_bin"); 726 | 727 | entity.Property(e => e.Password) 728 | .HasColumnName("password") 729 | .HasColumnType("varchar(255)") 730 | .HasCharSet("utf8mb4") 731 | .HasCollation("utf8mb4_bin"); 732 | 733 | entity.Property(e => e.Root) 734 | .HasColumnName("root") 735 | .HasColumnType("text") 736 | .HasCharSet("utf8mb4") 737 | .HasCollation("utf8mb4_bin"); 738 | 739 | entity.Property(e => e.UpdatedAt) 740 | .HasColumnName("updated_at") 741 | .HasColumnType("timestamp"); 742 | 743 | entity.Property(e => e.UserId) 744 | .HasColumnName("user_id") 745 | .HasColumnType("int(10) unsigned"); 746 | }); 747 | 748 | OnModelCreatingPartial(modelBuilder); 749 | } 750 | 751 | partial void OnModelCreatingPartial(ModelBuilder modelBuilder); 752 | } 753 | } 754 | -------------------------------------------------------------------------------- /EF/Downloads.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Downloads 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public int? Status { get; set; } 13 | public int? Type { get; set; } 14 | public string Source { get; set; } 15 | public ulong? TotalSize { get; set; } 16 | public ulong? DownloadedSize { get; set; } 17 | public string GId { get; set; } 18 | public int? Speed { get; set; } 19 | public string Parent { get; set; } 20 | public string Attrs { get; set; } 21 | public string Error { get; set; } 22 | public string Dst { get; set; } 23 | public uint? UserId { get; set; } 24 | public uint? TaskId { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /EF/Files.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Files 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Name { get; set; } 13 | public string SourceName { get; set; } 14 | public uint? UserId { get; set; } 15 | public ulong? Size { get; set; } 16 | public string PicInfo { get; set; } 17 | public uint? FolderId { get; set; } 18 | public uint? PolicyId { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /EF/Folders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Folders 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Name { get; set; } 13 | public uint? ParentId { get; set; } 14 | public uint? OwnerId { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /EF/Groups.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Groups 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Name { get; set; } 13 | public string Policies { get; set; } 14 | public ulong? MaxStorage { get; set; } 15 | public bool? ShareEnabled { get; set; } 16 | public bool? WebDavEnabled { get; set; } 17 | public int? SpeedLimit { get; set; } 18 | public string Options { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /EF/Policies.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Policies 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Name { get; set; } 13 | public string Type { get; set; } 14 | public string Server { get; set; } 15 | public string BucketName { get; set; } 16 | public bool? IsPrivate { get; set; } 17 | public string BaseUrl { get; set; } 18 | public string AccessKey { get; set; } 19 | public string SecretKey { get; set; } 20 | public ulong? MaxSize { get; set; } 21 | public bool? AutoRename { get; set; } 22 | public string DirNameRule { get; set; } 23 | public string FileNameRule { get; set; } 24 | public bool? IsOriginLinkEnable { get; set; } 25 | public string Options { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /EF/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Settings 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Type { get; set; } 13 | public string Name { get; set; } 14 | public string Value { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /EF/Shares.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Shares 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Password { get; set; } 13 | public bool? IsDir { get; set; } 14 | public uint? UserId { get; set; } 15 | public uint? SourceId { get; set; } 16 | public int? Views { get; set; } 17 | public int? Downloads { get; set; } 18 | public int? RemainDownloads { get; set; } 19 | public DateTime? Expires { get; set; } 20 | public bool? PreviewEnabled { get; set; } 21 | public string SourceName { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /EF/Tags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Tags 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Name { get; set; } 13 | public string Icon { get; set; } 14 | public string Color { get; set; } 15 | public int? Type { get; set; } 16 | public string Expression { get; set; } 17 | public uint? UserId { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /EF/Tasks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Tasks 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public int? Status { get; set; } 13 | public int? Type { get; set; } 14 | public uint? UserId { get; set; } 15 | public int? Progress { get; set; } 16 | public string Error { get; set; } 17 | public string Props { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /EF/Users.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Users 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Email { get; set; } 13 | public string Nick { get; set; } 14 | public string Password { get; set; } 15 | public int? Status { get; set; } 16 | public uint? GroupId { get; set; } 17 | public ulong? Storage { get; set; } 18 | public string TwoFactor { get; set; } 19 | public string Avatar { get; set; } 20 | public string Options { get; set; } 21 | public string Authn { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /EF/Webdavs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Cloudreve_FileSynchronizer.EF 5 | { 6 | public partial class Webdavs 7 | { 8 | public uint Id { get; set; } 9 | public DateTime? CreatedAt { get; set; } 10 | public DateTime? UpdatedAt { get; set; } 11 | public DateTime? DeletedAt { get; set; } 12 | public string Name { get; set; } 13 | public string Password { get; set; } 14 | public uint? UserId { get; set; } 15 | public string Root { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using Cloudreve_FileSynchronizer.EF; 2 | using Mono.Options; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Drawing; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace Cloudreve_FileSynchronizer 11 | { 12 | class Program 13 | { 14 | private const string IMAGE_EXT = ".jpg|.jpeg|.gif|.tiff|.png|.svg"; 15 | 16 | static void Main(string[] args) 17 | { 18 | ProgramParameter parameter = ParseParameter(args); 19 | 20 | if (parameter == null) 21 | return; 22 | 23 | if(!Directory.Exists(parameter.SourceDirectory)) 24 | { 25 | Console.WriteLine("Error: source directory does not exist"); 26 | return; 27 | } 28 | 29 | DirectoryInfo srcDir = new DirectoryInfo(parameter.SourceDirectory); 30 | 31 | Task.WaitAll(Task.Run(async () => 32 | { 33 | using (var dbContext = new CloudreveContext(parameter.ConnectionString)) 34 | { 35 | var user = dbContext.Users.FirstOrDefault(p => 36 | p.Email == parameter.UserName || p.Nick == parameter.UserName); 37 | 38 | if (user == null) 39 | { 40 | Console.WriteLine($"Error: user \"{parameter.UserName}\" does not exist"); 41 | return; 42 | } 43 | 44 | var policy = dbContext.Policies.FirstOrDefault(p => p.Id == parameter.StoragePolicy); 45 | 46 | if (policy == null) 47 | { 48 | Console.WriteLine($"Error: Storage policy (ID = \"{parameter.StoragePolicy}\") does not exist"); 49 | return; 50 | } 51 | 52 | Folders rootFolder = FindRootFolder(parameter.Target, dbContext); 53 | 54 | if (rootFolder == null) 55 | { 56 | Console.WriteLine($"target path \"{parameter.Target}\" does not exist in Cloudreve's database"); 57 | return; 58 | } 59 | else 60 | { 61 | Console.WriteLine($"target path \"{parameter.Target}\" found. ID = {rootFolder.Id}"); 62 | Console.WriteLine($"Synchronizing files and directories"); 63 | } 64 | 65 | var trans = await dbContext.Database.BeginTransactionAsync(); 66 | 67 | try 68 | { 69 | await SaveTree(srcDir, rootFolder, parameter.Target, user, policy, dbContext); 70 | 71 | await dbContext.SaveChangesAsync(); 72 | 73 | await trans.CommitAsync(); 74 | 75 | Console.WriteLine($"Transaction committed."); 76 | Console.WriteLine($"Successed."); 77 | } 78 | catch(Exception ex) 79 | { 80 | await trans.RollbackAsync(); 81 | 82 | Console.WriteLine($"An has error occured durning synchronization: {ex.Message}{(ex.InnerException == null ? "" : $" ({ex.InnerException.Message})")}"); 83 | Console.WriteLine("Stack trace:"); 84 | Console.WriteLine(ex.StackTrace); 85 | } 86 | finally 87 | { 88 | trans.Dispose(); 89 | } 90 | 91 | } 92 | })); 93 | 94 | } 95 | 96 | 97 | public static async Task SaveTree(DirectoryInfo dir, Folders parentDir, 98 | string thisDirPath, Users user, Policies policy, CloudreveContext dbContext) 99 | { 100 | string dirName = dir.Name; 101 | 102 | Console.WriteLine($" - Processing directory \"{thisDirPath}\""); 103 | 104 | var newFolder = dbContext.Folders.Add(new Folders() 105 | { 106 | OwnerId = user.Id, 107 | Name = dirName, 108 | ParentId = parentDir.Id, 109 | CreatedAt = DateTime.Now, 110 | UpdatedAt = DateTime.Now 111 | }); 112 | 113 | await dbContext.SaveChangesAsync(); 114 | 115 | Console.WriteLine($" - Folder \"{thisDirPath}\" created. ID = {newFolder.Entity.Id}"); 116 | 117 | var files = 118 | dir.GetFiles().Select(p => 119 | { 120 | string picinfo = null, fileFullPath; 121 | 122 | if (!String.IsNullOrEmpty(p.Extension) && IMAGE_EXT.Contains(p.Extension.ToLower())) 123 | picinfo = GetImageSizeStr(p); 124 | 125 | fileFullPath = (thisDirPath + "/" + p.Name); 126 | 127 | var file = new Files() 128 | { 129 | FolderId = newFolder.Entity.Id, 130 | Name = p.Name, 131 | PicInfo = picinfo, 132 | PolicyId = policy.Id, 133 | Size = (ulong)p.Length, 134 | SourceName = fileFullPath, 135 | UserId = user.Id, 136 | CreatedAt = DateTime.Now, 137 | UpdatedAt = DateTime.Now 138 | }; 139 | 140 | Console.WriteLine($" - File \"{fileFullPath}\" created."); 141 | 142 | return file; 143 | }); 144 | 145 | await dbContext.Files.AddRangeAsync(files); 146 | 147 | Console.WriteLine($" - {files.Count()} files created."); 148 | 149 | var subDirectories = dir.GetDirectories(); 150 | if (subDirectories.Length > 0) 151 | { 152 | Console.WriteLine($" - {subDirectories.Length} sub directories found. Processing sub directories..."); 153 | 154 | foreach (var subDir in subDirectories) 155 | await SaveTree(subDir, newFolder.Entity, (thisDirPath + "/" + subDir.Name), user, policy, dbContext); 156 | } 157 | else 158 | { 159 | Console.WriteLine($" - Leaf directory, leaving..."); 160 | } 161 | 162 | } 163 | 164 | private static string GetImageSizeStr(FileInfo p) 165 | { 166 | string picinfo = null; 167 | FileStream imageFileStream = null; 168 | Image image = null; 169 | try 170 | { 171 | imageFileStream = p.Open(FileMode.Open); 172 | 173 | image = Image.FromStream(imageFileStream, false, false); 174 | 175 | picinfo = $"{image.Width},{image.Height}"; 176 | 177 | } 178 | catch(Exception ex) 179 | { 180 | Console.WriteLine($" - \"{p.Name}\" Failed to get image dimensions: {ex.Message}"); 181 | } 182 | finally 183 | { 184 | if (imageFileStream != null) 185 | imageFileStream.Dispose(); 186 | 187 | if (image != null) 188 | image.Dispose(); 189 | } 190 | 191 | return picinfo; 192 | } 193 | 194 | private static Folders FindRootFolder(string targetPath, CloudreveContext dbContext) 195 | { 196 | Folders rootFolder = null; 197 | 198 | if (targetPath == "/") 199 | { 200 | rootFolder = dbContext.Folders.FirstOrDefault(p => p.Name == "/"); 201 | } 202 | else 203 | { 204 | 205 | var pathParts = targetPath.Split('/'); 206 | 207 | if (String.IsNullOrWhiteSpace(pathParts.Last())) 208 | pathParts = pathParts.Take(pathParts.Length - 1).ToArray(); 209 | 210 | var beginning = pathParts.Last(); 211 | 212 | var folderRecord = dbContext.Folders.Where(p => p.Name == beginning).ToArray(); 213 | 214 | foreach (var rec in folderRecord) 215 | { 216 | int i = pathParts.Length - 1; 217 | var cur = rec; 218 | 219 | do i--;while (i > 0 && (cur = dbContext.Folders.FirstOrDefault(p => p.Id == cur.ParentId))?.Name == pathParts[i]); 220 | 221 | if (i == 0) 222 | { 223 | rootFolder = rec; 224 | break; 225 | } 226 | } 227 | } 228 | 229 | return rootFolder; 230 | } 231 | 232 | private static ProgramParameter ParseParameter(string[] args) 233 | { 234 | ProgramParameter programParameter = new ProgramParameter(); 235 | bool success = true; 236 | 237 | //string connectionString = "Server={SERVER};Port={PORT};database={DATABASE};uid={UID};pwd={PWD};SslMode=None"; 238 | var opt = new OptionSet() { 239 | { 240 | "s|source-dir=", "source directory to upload", 241 | v => { 242 | 243 | if(String.IsNullOrEmpty(v)) 244 | { 245 | Console.WriteLine("source directory cannot be empty"); 246 | success = false; 247 | } 248 | else 249 | programParameter.SourceDirectory = v; 250 | } 251 | }, 252 | { 253 | "n|user-name=", "upload for whom", 254 | v => 255 | { 256 | if(String.IsNullOrEmpty(v)) 257 | { 258 | Console.WriteLine("user name cannot be empty"); 259 | success = false; 260 | } 261 | else 262 | programParameter.UserName = v; 263 | } 264 | }, 265 | { 266 | "p|policy-id=", "storage policy id", 267 | v => 268 | { 269 | if(String.IsNullOrEmpty(v)) 270 | { 271 | Console.WriteLine("storage policy id cannot be empty"); 272 | success = false; 273 | } 274 | 275 | programParameter.StoragePolicy = Int32.Parse(v); 276 | } 277 | }, 278 | { 279 | "t|target=", "base directory path", 280 | v => 281 | { 282 | if(String.IsNullOrEmpty(v)) 283 | { 284 | Console.WriteLine("target directory cannot be empty"); 285 | success = false; 286 | } 287 | else if (!v.StartsWith("/")) 288 | { 289 | Console.WriteLine("target must start with \"/\""); 290 | success = false; 291 | } 292 | 293 | else 294 | programParameter.Target = v; 295 | } 296 | }, 297 | { 298 | "c|connection-string=", "Connection string to a mysql instance", 299 | v => 300 | { 301 | if(String.IsNullOrEmpty(v)) 302 | { 303 | Console.WriteLine("connection string cannot be empty"); 304 | success = false; 305 | } 306 | else 307 | { 308 | programParameter.ConnectionString = v; 309 | } 310 | 311 | 312 | } 313 | }, 314 | { 315 | "h|help", "show help", 316 | v => 317 | { 318 | //do nothing 319 | } 320 | } 321 | }; 322 | 323 | if (args == null || args.Length == 0 || args.Any(p => p == "-h" || p == "--help")) 324 | { 325 | ShowHelp(opt); 326 | return null; 327 | } 328 | 329 | List extra; 330 | try 331 | { 332 | extra = opt.Parse(args); 333 | 334 | if (!success) 335 | return null; 336 | 337 | return programParameter; 338 | } 339 | catch (OptionException e) 340 | { 341 | Console.Write("invalid parameter: "); 342 | Console.WriteLine(e.Message); 343 | Console.WriteLine("Try `--help' for more information."); 344 | return null; 345 | } 346 | } 347 | 348 | private static void ShowHelp(OptionSet p) 349 | { 350 | Console.WriteLine("This tool is used to post-record data for files that not uploaded by Cloudreve but uploaded via ftp or disk copy."); 351 | Console.WriteLine("This won't perform a real file copy but just make files under control in Cloudreve."); 352 | Console.WriteLine("this tool only work for Cloudreve version 3.0+ (using MySQL database)"); 353 | Console.WriteLine(); 354 | Console.WriteLine("Options:"); 355 | p.WriteOptionDescriptions(Console.Out); 356 | } 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /ProgramParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Cloudreve_FileSynchronizer 6 | { 7 | public class ProgramParameter 8 | { 9 | private string sourceDirectory; 10 | private string target; 11 | private string userName; 12 | private string connectionString; 13 | private bool showHelp; 14 | private int storagePolicy; 15 | 16 | public string SourceDirectory { get => sourceDirectory; set => sourceDirectory = value?.Trim(); } 17 | public string Target { get => target; set => target = value?.Trim(); } 18 | public string UserName { get => userName; set => userName = value?.Trim(); } 19 | public string ConnectionString { get => connectionString; set => connectionString = value?.Trim(); } 20 | public bool ShowHelp { get => showHelp; set => showHelp = value; } 21 | public int StoragePolicy { get => storagePolicy; set => storagePolicy = value; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Cloudreve_FileSynchronizer": { 4 | "commandName": "Project", 5 | "commandLineArgs": "" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudreve File Synchronizer 2 | 3 | A simple post-offline-copy file list synchronizer. 4 | 5 | - This tool is used to post-record data for files that not uploaded by Cloudreve but uploaded via ftp or disk copy. 6 | - This tool won't perform a real file copy but just make files under control in Cloudreve. 7 | - Only work for Cloudreve version 3.0+ (using MySQL database) 8 | 9 | 10 | 11 | # Usage: 12 | 13 | ``` 14 | Cloudreve_FileSynchronizer -s /source/directory -t /target/path/in/cloudreve -n user-name -p policy-id -c "Server=xxx;Port=3306;database=Cloudreve_Database;uid=xxxx;pwd=xxx;" 15 | ``` 16 | 17 | Options: 18 | 19 | -s, --source-dir=VALUE source directory to upload 20 | 21 | -n, --user-name=VALUE upload for whom 22 | 23 | -p, --policy-id=VALUE storage policy id 24 | 25 | -t, --target=VALUE base directory path 26 | 27 | -c, --connection-string=VALUE Connection string to a mysql instance 28 | 29 | -h, --help show help 30 | 31 | # Requirements 32 | .NET Core 3.1 33 | 34 | libgdiplus (Linux) 35 | --------------------------------------------------------------------------------