1/* 2* Copyright (c) 2010-2013 Yorba Foundation 3* 4* This program is free software; you can redistribute it and/or 5* modify it under the terms of the GNU Lesser General Public 6* License as published by the Free Software Foundation; either 7* version 2.1 of the License, or (at your option) any later version. 8* 9* This program is distributed in the hope that it will be useful, 10* but WITHOUT ANY WARRANTY; without even the implied warranty of 11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12* General Public License for more details. 13* 14* You should have received a copy of the GNU General Public 15* License along with this program; if not, write to the 16* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17* Boston, MA 02110-1301 USA 18*/ 19 20private class PhotoUpdates : MonitorableUpdates { 21 public LibraryPhoto photo; 22 23 public bool reimport_master = false; 24 public bool reimport_editable = false; 25 public bool reimport_raw_developments = false; 26 public File? editable_file = null; 27 public bool editable_file_info_altered = false; 28 public bool raw_developer_file_info_altered = false; 29 public FileInfo? editable_file_info = null; 30 public bool editable_in_alteration = false; 31 public bool raw_development_in_alteration = false; 32 public bool revert_to_master = false; 33 public Gee.Collection<File> developer_files = new Gee.ArrayList<File> (); 34 35 public PhotoUpdates (LibraryPhoto photo) { 36 base (photo); 37 38 this.photo = photo; 39 } 40 41 public override void mark_offline () { 42 base.mark_offline (); 43 44 reimport_master = false; 45 reimport_editable = false; 46 reimport_raw_developments = false; 47 } 48 49 public bool is_reimport_master () { 50 return reimport_master; 51 } 52 53 public bool is_reimport_editable () { 54 return reimport_editable; 55 } 56 57 public File? get_editable_file () { 58 return editable_file; 59 } 60 61 public FileInfo? get_editable_file_info () { 62 return editable_file_info; 63 } 64 65 public Gee.Collection<File> get_raw_developer_files () { 66 return developer_files; 67 } 68 69 public override bool is_in_alteration () { 70 return base.is_in_alteration () || editable_in_alteration; 71 } 72 73 public bool is_revert_to_master () { 74 return revert_to_master; 75 } 76 77 public virtual void set_editable_file (File? file) { 78 // if reverting, don't bother 79 if (file != null && revert_to_master) 80 return; 81 82 editable_file = file; 83 } 84 85 public virtual void set_editable_file_info (FileInfo? info) { 86 // if reverting, don't bother 87 if (info != null && revert_to_master) 88 return; 89 90 editable_file_info = info; 91 if (info == null) 92 editable_file_info_altered = false; 93 } 94 95 public virtual void set_editable_file_info_altered (bool altered) { 96 // if reverting, don't bother 97 if (altered && revert_to_master) 98 return; 99 100 editable_file_info_altered = altered; 101 } 102 103 public virtual void set_editable_in_alteration (bool in_alteration) { 104 editable_in_alteration = in_alteration; 105 } 106 107 public virtual void set_raw_development_in_alteration (bool in_alteration) { 108 raw_development_in_alteration = in_alteration; 109 } 110 111 public virtual void set_raw_developer_file_info_altered (bool altered) { 112 raw_developer_file_info_altered = altered; 113 } 114 115 public virtual void set_revert_to_master (bool revert) { 116 if (revert) { 117 // this means nothing any longer 118 reimport_editable = false; 119 editable_file = null; 120 editable_file_info = null; 121 } 122 123 revert_to_master = revert; 124 } 125 126 public virtual void add_raw_developer_file (File file) { 127 developer_files.add (file); 128 } 129 130 public virtual void clear_raw_developer_files () { 131 developer_files.clear (); 132 } 133 134 public virtual void set_reimport_master (bool reimport) { 135 reimport_master = reimport; 136 137 if (reimport) 138 mark_online (); 139 } 140 141 public virtual void set_reimport_editable (bool reimport) { 142 // if reverting or going offline, don't bother 143 if (reimport && (revert_to_master || is_set_offline ())) 144 return; 145 146 reimport_editable = reimport; 147 } 148 149 public virtual void set_reimport_raw_developments (bool reimport) { 150 reimport_raw_developments = reimport; 151 152 if (reimport) 153 mark_online (); 154 } 155 156 public override bool is_all_updated () { 157 return base.is_all_updated () 158 && reimport_master == false 159 && reimport_editable == false 160 && editable_file == null 161 && editable_file_info_altered == false 162 && editable_file_info == null 163 && editable_in_alteration == false 164 && developer_files.size == 0 165 && raw_developer_file_info_altered == false 166 && revert_to_master == false; 167 } 168} 169 170private class PhotoMonitor : MediaMonitor { 171 private const int MAX_REIMPORT_JOBS_PER_CYCLE = 20; 172 private const int MAX_REVERTS_PER_CYCLE = 5; 173 174 private class ReimportMasterJob : BackgroundJob { 175 public LibraryPhoto photo; 176 public Photo.ReimportMasterState reimport_state = null; 177 public bool mark_online = false; 178 public Error err = null; 179 180 public ReimportMasterJob (PhotoMonitor owner, LibraryPhoto photo) { 181 base (owner, owner.on_master_reimported, new Cancellable (), 182 owner.on_master_reimport_cancelled); 183 184 this.photo = photo; 185 } 186 187 public override void execute () { 188 try { 189 mark_online = photo.prepare_for_reimport_master (out reimport_state); 190 } catch (Error err) { 191 this.err = err; 192 } 193 } 194 } 195 196 private class ReimportEditableJob : BackgroundJob { 197 public LibraryPhoto photo; 198 public Photo.ReimportEditableState state = null; 199 public bool success = false; 200 public Error err = null; 201 202 public ReimportEditableJob (PhotoMonitor owner, LibraryPhoto photo) { 203 base (owner, owner.on_editable_reimported, new Cancellable (), 204 owner.on_editable_reimport_cancelled); 205 206 this.photo = photo; 207 } 208 209 public override void execute () { 210 try { 211 success = photo.prepare_for_reimport_editable (out state); 212 } catch (Error err) { 213 this.err = err; 214 } 215 } 216 } 217 218 private class ReimportRawDevelopmentJob : BackgroundJob { 219 public LibraryPhoto photo; 220 public Photo.ReimportRawDevelopmentState state = null; 221 public bool success = false; 222 public Error err = null; 223 224 public ReimportRawDevelopmentJob (PhotoMonitor owner, LibraryPhoto photo) { 225 base (owner, owner.on_raw_development_reimported, new Cancellable (), 226 owner.on_raw_development_reimport_cancelled); 227 228 this.photo = photo; 229 } 230 231 public override void execute () { 232 try { 233 success = photo.prepare_for_reimport_raw_development (out state); 234 } catch (Error err) { 235 this.err = err; 236 } 237 } 238 } 239 240 private Workers workers; 241 private Gee.ArrayList<LibraryPhoto> matched_editables = new Gee.ArrayList<LibraryPhoto> (); 242 private Gee.ArrayList<LibraryPhoto> matched_developments = new Gee.ArrayList<LibraryPhoto> (); 243 private Gee.HashMap<LibraryPhoto, ReimportMasterJob> master_reimport_pending = new Gee.HashMap < 244 LibraryPhoto, ReimportMasterJob > (); 245 private Gee.HashMap<LibraryPhoto, ReimportEditableJob> editable_reimport_pending = 246 new Gee.HashMap<LibraryPhoto, ReimportEditableJob> (); 247 private Gee.HashMap<LibraryPhoto, ReimportRawDevelopmentJob> raw_developments_reimport_pending = 248 new Gee.HashMap<LibraryPhoto, ReimportRawDevelopmentJob> (); 249 250 public PhotoMonitor (Workers workers, Cancellable cancellable) { 251 base (LibraryPhoto.global, cancellable); 252 253 this.workers = workers; 254 } 255 256 protected override MonitorableUpdates create_updates (Monitorable monitorable) { 257 assert (monitorable is LibraryPhoto); 258 259 return new PhotoUpdates ((LibraryPhoto) monitorable); 260 } 261 262 public override MediaSourceCollection get_media_source_collection () { 263 return LibraryPhoto.global; 264 } 265 266 public override bool is_file_represented (File file) { 267 LibraryPhotoSourceCollection.State state; 268 return get_photo_state_by_file (file, out state) != null; 269 } 270 271 public override void close () { 272 foreach (ReimportMasterJob job in master_reimport_pending.values) 273 job.cancel (); 274 275 foreach (ReimportEditableJob job in editable_reimport_pending.values) 276 job.cancel (); 277 278 foreach (ReimportRawDevelopmentJob job in raw_developments_reimport_pending.values) 279 job.cancel (); 280 281 base.close (); 282 } 283 284 private void cancel_reimports (LibraryPhoto photo) { 285 ReimportMasterJob? master_job = master_reimport_pending.get (photo); 286 if (master_job != null) 287 master_job.cancel (); 288 289 ReimportEditableJob? editable_job = editable_reimport_pending.get (photo); 290 if (editable_job != null) 291 editable_job.cancel (); 292 } 293 294 public override MediaMonitor.DiscoveredFile notify_file_discovered (File file, FileInfo info, 295 out Monitorable monitorable) { 296 LibraryPhotoSourceCollection.State state; 297 LibraryPhoto? photo = get_photo_state_by_file (file, out state); 298 if (photo == null) { 299 monitorable = null; 300 301 return MediaMonitor.DiscoveredFile.UNKNOWN; 302 } 303 304 switch (state) { 305 case LibraryPhotoSourceCollection.State.ONLINE: 306 case LibraryPhotoSourceCollection.State.OFFLINE: 307 monitorable = photo; 308 309 return MediaMonitor.DiscoveredFile.REPRESENTED; 310 311 case LibraryPhotoSourceCollection.State.TRASH: 312 case LibraryPhotoSourceCollection.State.EDITABLE: 313 case LibraryPhotoSourceCollection.State.DEVELOPER: 314 default: 315 // ignored ... trash always stays in trash, offline or not, and editables are 316 // simply attached to online/offline photos 317 monitorable = null; 318 319 return MediaMonitor.DiscoveredFile.IGNORE; 320 } 321 } 322 323 public override Gee.Collection<Monitorable>? candidates_for_unknown_file (File file, FileInfo info, 324 out MediaMonitor.DiscoveredFile result) { 325 // reset with each call 326 matched_editables.clear (); 327 matched_developments.clear (); 328 329 Gee.Collection<LibraryPhoto> matched_masters = new Gee.ArrayList<LibraryPhoto> (); 330 LibraryPhoto.global.fetch_by_matching_backing (info, matched_masters, matched_editables, 331 matched_developments); 332 if (matched_masters.size > 0) { 333 result = MediaMonitor.DiscoveredFile.UNKNOWN; 334 335 return matched_masters; 336 } 337 338 if (matched_editables.size == 0 && matched_developments.size == 0) { 339 result = MediaMonitor.DiscoveredFile.UNKNOWN; 340 341 return null; 342 } 343 344 // for editable files and raw developments, trust file characteristics alone 345 if (matched_editables.size > 0) { 346 LibraryPhoto match = matched_editables[0]; 347 if (matched_editables.size > 1) { 348 warning ("Unknown file %s could be matched with %d photos; giving to %s, dropping others", 349 file.get_path (), matched_editables.size, match.to_string ()); 350 for (int ctr = 1; ctr < matched_editables.size; ctr++) { 351 if (!matched_editables[ctr].does_editable_exist ()) 352 matched_editables[ctr].revert_to_master (); 353 } 354 } 355 356 update_editable_file (match, file); 357 } 358 359 if (matched_developments.size > 0) { 360 LibraryPhoto match_raw = matched_developments[0]; 361 if (matched_developments.size > 1) { 362 warning ("Unknown file %s could be matched with %d photos; giving to %s, dropping others", 363 file.get_path (), matched_developments.size, match_raw.to_string ()); 364 } 365 366 update_raw_development_file (match_raw, file); 367 } 368 369 result = MediaMonitor.DiscoveredFile.IGNORE; 370 371 return null; 372 } 373 374 public override File[]? get_auxilliary_backing_files (Monitorable monitorable) { 375 LibraryPhoto photo = (LibraryPhoto) monitorable; 376 File[] files = new File[0]; 377 378 // Editable. 379 if (photo.has_editable ()) 380 files += photo.get_editable_file (); 381 382 // Raw developments. 383 Gee.Collection<File>? raw_files = photo.get_raw_developer_files (); 384 if (raw_files != null) 385 foreach (File f in raw_files) 386 files += f; 387 388 // Return null if no files. 389 return files.length > 0 ? files : null; 390 } 391 392 public override void update_backing_file_info (Monitorable monitorable, File file, FileInfo? info) { 393 LibraryPhoto photo = (LibraryPhoto) monitorable; 394 395 if (get_master_file (photo).equal (file)) 396 check_for_master_changes (photo, info); 397 else if (get_editable_file (photo) != null && get_editable_file (photo).equal (file)) 398 check_for_editable_changes (photo, info); 399 else if (get_raw_development_files (photo) != null) { 400 foreach (File f in get_raw_development_files (photo)) { 401 if (f.equal (file)) 402 check_for_raw_development_changes (photo, info); 403 } 404 } 405 } 406 407 public override void notify_discovery_completing () { 408 matched_editables.clear (); 409 } 410 411 // If filesize has changed, treat that as a full-blown modification 412 // and reimport ... this is problematic if only the metadata has changed, but so be it. 413 // 414 // TODO: We could do an MD5 check for more accuracy. 415 private void check_for_master_changes (LibraryPhoto photo, FileInfo? info) { 416 // if not present, offline state is already taken care of by LibraryMonitor 417 if (info == null) 418 return; 419 420 BackingPhotoRow state = photo.get_master_photo_row (); 421 if (state.matches_file_info (info)) 422 return; 423 424 if (state.is_touched (info)) { 425 update_master_file_info_altered (photo); 426 update_master_file_alterations_completed (photo, info); 427 } else { 428 update_reimport_master (photo); 429 } 430 } 431 432 private void check_for_editable_changes (LibraryPhoto photo, FileInfo? info) { 433 if (info == null) { 434 update_revert_to_master (photo); 435 436 return; 437 } 438 439 // If state matches, done -- editables have no bearing on a photo's offline status. 440 BackingPhotoRow? state = photo.get_editable_photo_row (); 441 if (state == null || state.matches_file_info (info)) 442 return; 443 444 if (state.is_touched (info)) { 445 update_editable_file_info_altered (photo); 446 update_editable_file_alterations_completed (photo, info); 447 } else { 448 update_reimport_editable (photo); 449 } 450 } 451 452 private void check_for_raw_development_changes (LibraryPhoto photo, FileInfo? info) { 453 if (info == null) { 454 // Switch back to default for safety. 455 photo.set_raw_developer (RawDeveloper.SHOTWELL); 456 457 return; 458 } 459 460 Gee.Collection<BackingPhotoRow>? rows = photo.get_raw_development_photo_rows (); 461 if (rows == null) 462 return; 463 464 // Look through all possible rows, if we find a file with a matching name or info, 465 // assume we found our man. 466 foreach (BackingPhotoRow row in rows) { 467 if (row.matches_file_info (info)) 468 return; 469 if (info.get_name () == row.filepath) { 470 if (row.is_touched (info)) { 471 update_raw_development_file_info_altered (photo); 472 update_raw_development_file_alterations_completed (photo); 473 } else { 474 update_reimport_raw_developments (photo); 475 } 476 477 break; 478 } 479 } 480 } 481 482 public override bool notify_file_created (File file, FileInfo info) { 483 LibraryPhotoSourceCollection.State state; 484 LibraryPhoto? photo = get_photo_state_by_file (file, out state); 485 if (photo == null) 486 return false; 487 488 switch (state) { 489 case LibraryPhotoSourceCollection.State.ONLINE: 490 case LibraryPhotoSourceCollection.State.TRASH: 491 case LibraryPhotoSourceCollection.State.EDITABLE: 492 case LibraryPhotoSourceCollection.State.DEVELOPER: 493 // do nothing, although this is unexpected 494 warning ("File %s created in %s state", file.get_path (), state.to_string ()); 495 break; 496 497 case LibraryPhotoSourceCollection.State.OFFLINE: 498 mdbg ("Will mark %s online".printf (photo.to_string ())); 499 update_online (photo); 500 break; 501 502 default: 503 error ("Unknown LibraryPhoto collection state %s", state.to_string ()); 504 } 505 506 return true; 507 } 508 509 public override bool notify_file_moved (File old_file, File new_file, FileInfo info) { 510 LibraryPhotoSourceCollection.State old_state; 511 LibraryPhoto? old_photo = get_photo_state_by_file (old_file, out old_state); 512 513 LibraryPhotoSourceCollection.State new_state; 514 LibraryPhoto? new_photo = get_photo_state_by_file (new_file, out new_state); 515 516 // Four possibilities: 517 // 518 // 1. Moving an existing photo file to a location where no photo is represented 519 // Operation: have the Photo object move with the file. 520 // 2. Moving a file with no representative photo to a location where a photo is represented 521 // (i.e. is offline). Operation: Update the photo (backing has changed). 522 // 3. Moving a file with no representative photo to a location with no representative 523 // photo. Operation: Enqueue for import (if appropriate). 524 // 4. Move a file with a representative photo to a location where a photo is represented 525 // Operation: Mark the old photo as offline (or drop editable) and update new photo 526 // (the backing has changed). 527 528 if (old_photo != null && new_photo == null) { 529 // 1. 530 switch (old_state) { 531 case LibraryPhotoSourceCollection.State.ONLINE: 532 case LibraryPhotoSourceCollection.State.TRASH: 533 case LibraryPhotoSourceCollection.State.OFFLINE: 534 mdbg ("Will set new master file for %s to %s".printf (old_photo.to_string (), 535 new_file.get_path ())); 536 update_master_file (old_photo, new_file); 537 break; 538 539 case LibraryPhotoSourceCollection.State.EDITABLE: 540 mdbg ("Will set new editable file for %s to %s".printf (old_photo.to_string (), 541 new_file.get_path ())); 542 update_editable_file (old_photo, new_file); 543 break; 544 545 case LibraryPhotoSourceCollection.State.DEVELOPER: 546 mdbg ("Will set new raw development file for %s to %s".printf (old_photo.to_string (), 547 new_file.get_path ())); 548 update_raw_development_file (old_photo, new_file); 549 break; 550 551 default: 552 error ("Unknown LibraryPhoto collection state %s", old_state.to_string ()); 553 } 554 } else if (old_photo == null && new_photo != null) { 555 // 2. 556 switch (new_state) { 557 case LibraryPhotoSourceCollection.State.ONLINE: 558 case LibraryPhotoSourceCollection.State.TRASH: 559 case LibraryPhotoSourceCollection.State.OFFLINE: 560 mdbg ("Will reimport master file for %s".printf (new_photo.to_string ())); 561 update_reimport_master (new_photo); 562 break; 563 564 case LibraryPhotoSourceCollection.State.EDITABLE: 565 mdbg ("Will reimport editable file for %s".printf (new_photo.to_string ())); 566 update_reimport_editable (new_photo); 567 break; 568 569 case LibraryPhotoSourceCollection.State.DEVELOPER: 570 mdbg ("Will reimport raw development file for %s".printf (new_photo.to_string ())); 571 update_reimport_raw_developments (new_photo); 572 break; 573 574 default: 575 error ("Unknown LibraryPhoto collection state %s", new_state.to_string ()); 576 } 577 } else if (old_photo == null && new_photo == null) { 578 // 3. 579 return false; 580 } else { 581 assert (old_photo != null && new_photo != null); 582 // 4. 583 switch (old_state) { 584 case LibraryPhotoSourceCollection.State.ONLINE: 585 mdbg ("Will mark offline %s".printf (old_photo.to_string ())); 586 update_offline (old_photo); 587 break; 588 589 case LibraryPhotoSourceCollection.State.TRASH: 590 case LibraryPhotoSourceCollection.State.OFFLINE: 591 // do nothing 592 break; 593 594 case LibraryPhotoSourceCollection.State.EDITABLE: 595 mdbg ("Will revert %s to master".printf (old_photo.to_string ())); 596 update_revert_to_master (old_photo); 597 break; 598 599 case LibraryPhotoSourceCollection.State.DEVELOPER: 600 // do nothing 601 break; 602 603 default: 604 error ("Unknown LibraryPhoto collection state %s", old_state.to_string ()); 605 } 606 607 switch (new_state) { 608 case LibraryPhotoSourceCollection.State.ONLINE: 609 case LibraryPhotoSourceCollection.State.TRASH: 610 case LibraryPhotoSourceCollection.State.OFFLINE: 611 mdbg ("Will reimport master file for %s".printf (new_photo.to_string ())); 612 update_reimport_master (new_photo); 613 break; 614 615 case LibraryPhotoSourceCollection.State.EDITABLE: 616 mdbg ("Will reimport editable file for %s".printf (new_photo.to_string ())); 617 update_reimport_editable (new_photo); 618 break; 619 620 case LibraryPhotoSourceCollection.State.DEVELOPER: 621 mdbg ("Will reimport raw development file for %s".printf (new_photo.to_string ())); 622 update_reimport_raw_developments (new_photo); 623 break; 624 625 default: 626 error ("Unknown LibraryPhoto collection state %s", new_state.to_string ()); 627 } 628 } 629 630 return true; 631 } 632 633 public override bool notify_file_altered (File file) { 634 LibraryPhotoSourceCollection.State state; 635 LibraryPhoto? photo = get_photo_state_by_file (file, out state); 636 if (photo == null) 637 return false; 638 639 switch (state) { 640 case LibraryPhotoSourceCollection.State.ONLINE: 641 case LibraryPhotoSourceCollection.State.OFFLINE: 642 case LibraryPhotoSourceCollection.State.TRASH: 643 mdbg ("Will reimport master for %s".printf (photo.to_string ())); 644 update_reimport_master (photo); 645 update_master_file_in_alteration (photo, true); 646 break; 647 648 case LibraryPhotoSourceCollection.State.EDITABLE: 649 mdbg ("Will reimport editable for %s".printf (photo.to_string ())); 650 update_reimport_editable (photo); 651 update_editable_file_in_alteration (photo, true); 652 break; 653 654 case LibraryPhotoSourceCollection.State.DEVELOPER: 655 mdbg ("Will reimport raw development for %s".printf (photo.to_string ())); 656 update_reimport_raw_developments (photo); 657 update_raw_development_file_in_alteration (photo, true); 658 break; 659 660 default: 661 error ("Unknown LibraryPhoto collection state %s", state.to_string ()); 662 } 663 664 return true; 665 } 666 667 public override bool notify_file_attributes_altered (File file) { 668 LibraryPhotoSourceCollection.State state; 669 LibraryPhoto? photo = get_photo_state_by_file (file, out state); 670 if (photo == null) 671 return false; 672 673 switch (state) { 674 case LibraryPhotoSourceCollection.State.ONLINE: 675 case LibraryPhotoSourceCollection.State.TRASH: 676 mdbg ("Will update master file info for %s".printf (photo.to_string ())); 677 update_master_file_info_altered (photo); 678 update_master_file_in_alteration (photo, true); 679 break; 680 681 case LibraryPhotoSourceCollection.State.OFFLINE: 682 // do nothing, but unexpected 683 warning ("File %s attributes altered in %s state", file.get_path (), 684 state.to_string ()); 685 update_master_file_in_alteration (photo, true); 686 break; 687 688 case LibraryPhotoSourceCollection.State.EDITABLE: 689 mdbg ("Will update editable file info for %s".printf (photo.to_string ())); 690 update_editable_file_info_altered (photo); 691 update_editable_file_in_alteration (photo, true); 692 break; 693 694 case LibraryPhotoSourceCollection.State.DEVELOPER: 695 mdbg ("Will update raw development file info for %s".printf (photo.to_string ())); 696 update_raw_development_file_info_altered (photo); 697 update_raw_development_file_in_alteration (photo, true); 698 break; 699 700 default: 701 error ("Unknown LibraryPhoto collection state %s", state.to_string ()); 702 } 703 704 return true; 705 } 706 707 public override bool notify_file_alteration_completed (File file, FileInfo info) { 708 LibraryPhotoSourceCollection.State state; 709 LibraryPhoto? photo = get_photo_state_by_file (file, out state); 710 if (photo == null) 711 return false; 712 713 switch (state) { 714 case LibraryPhotoSourceCollection.State.ONLINE: 715 case LibraryPhotoSourceCollection.State.TRASH: 716 case LibraryPhotoSourceCollection.State.OFFLINE: 717 update_master_file_alterations_completed (photo, info); 718 break; 719 720 case LibraryPhotoSourceCollection.State.EDITABLE: 721 update_editable_file_alterations_completed (photo, info); 722 break; 723 724 case LibraryPhotoSourceCollection.State.DEVELOPER: 725 update_raw_development_file_alterations_completed (photo); 726 break; 727 728 default: 729 error ("Unknown LibraryPhoto collection state %s", state.to_string ()); 730 } 731 732 return true; 733 } 734 735 public override bool notify_file_deleted (File file) { 736 LibraryPhotoSourceCollection.State state; 737 LibraryPhoto? photo = get_photo_state_by_file (file, out state); 738 if (photo == null) 739 return false; 740 741 switch (state) { 742 case LibraryPhotoSourceCollection.State.ONLINE: 743 mdbg ("Will mark %s offline".printf (photo.to_string ())); 744 update_offline (photo); 745 update_master_file_in_alteration (photo, false); 746 break; 747 748 case LibraryPhotoSourceCollection.State.TRASH: 749 case LibraryPhotoSourceCollection.State.OFFLINE: 750 // do nothing / already knew this 751 update_master_file_in_alteration (photo, false); 752 break; 753 754 case LibraryPhotoSourceCollection.State.EDITABLE: 755 mdbg ("Will revert %s to master".printf (photo.to_string ())); 756 update_revert_to_master (photo); 757 update_editable_file_in_alteration (photo, false); 758 break; 759 760 case LibraryPhotoSourceCollection.State.DEVELOPER: 761 mdbg ("Will revert %s to master".printf (photo.to_string ())); 762 update_revert_to_master (photo); 763 update_editable_file_in_alteration (photo, false); 764 update_raw_development_file_in_alteration (photo, false); 765 break; 766 767 default: 768 error ("Unknown LibraryPhoto collection state %s", state.to_string ()); 769 } 770 771 return true; 772 } 773 774 protected override void on_media_source_destroyed (DataSource source) { 775 base.on_media_source_destroyed (source); 776 777 cancel_reimports ((LibraryPhoto) source); 778 } 779 780 private LibraryPhoto? get_photo_state_by_file (File file, out LibraryPhotoSourceCollection.State state) { 781 File? real_file = null; 782 if (has_pending_updates ()) { 783 foreach (Monitorable monitorable in get_monitorables ()) { 784 LibraryPhoto photo = (LibraryPhoto) monitorable; 785 786 PhotoUpdates? updates = get_existing_photo_updates (photo); 787 if (updates == null) 788 continue; 789 790 if (updates.get_master_file () != null && updates.get_master_file ().equal (file)) { 791 real_file = photo.get_master_file (); 792 793 break; 794 } 795 796 if (updates.get_editable_file () != null && updates.get_editable_file ().equal (file)) { 797 real_file = photo.get_editable_file (); 798 799 // if the photo's "real" editable file is null, then this file hasn't been 800 // associated with it (yet) so fake the call 801 if (real_file == null) { 802 state = LibraryPhotoSourceCollection.State.EDITABLE; 803 804 return photo; 805 } 806 807 break; 808 } 809 810 if (updates.get_raw_developer_files () != null) { 811 bool found = false; 812 foreach (File raw in updates.get_raw_developer_files ()) { 813 if (raw.equal (file)) { 814 found = true; 815 816 break; 817 } 818 } 819 820 if (found) { 821 Gee.Collection<File>? developed = photo.get_raw_developer_files (); 822 if (developed != null) { 823 foreach (File f in developed) { 824 if (f.equal (file)) { 825 real_file = f; 826 state = LibraryPhotoSourceCollection.State.DEVELOPER; 827 828 break; 829 } 830 } 831 832 } 833 834 break; 835 } 836 } 837 } 838 } 839 840 return LibraryPhoto.global.get_state_by_file (real_file ?? file, out state); 841 } 842 843 public PhotoUpdates fetch_photo_updates (LibraryPhoto photo) { 844 return (PhotoUpdates) fetch_updates (photo); 845 } 846 847 public PhotoUpdates? get_existing_photo_updates (LibraryPhoto photo) { 848 return get_existing_updates (photo) as PhotoUpdates; 849 } 850 851 public void update_reimport_master (LibraryPhoto photo) { 852 fetch_photo_updates (photo).set_reimport_master (true); 853 854 // cancel outstanding reimport 855 if (master_reimport_pending.has_key (photo)) 856 master_reimport_pending.get (photo).cancel (); 857 } 858 859 public void update_reimport_editable (LibraryPhoto photo) { 860 fetch_photo_updates (photo).set_reimport_editable (true); 861 862 // cancel outstanding reimport 863 if (editable_reimport_pending.has_key (photo)) 864 editable_reimport_pending.get (photo).cancel (); 865 } 866 867 public void update_reimport_raw_developments (LibraryPhoto photo) { 868 fetch_photo_updates (photo).set_reimport_raw_developments (true); 869 870 // cancel outstanding reimport 871 if (raw_developments_reimport_pending.has_key (photo)) 872 raw_developments_reimport_pending.get (photo).cancel (); 873 } 874 875 public File? get_editable_file (LibraryPhoto photo) { 876 PhotoUpdates? updates = get_existing_photo_updates (photo); 877 878 return (updates != null && updates.get_editable_file () != null) ? updates.get_editable_file () 879 : photo.get_editable_file (); 880 } 881 882 public Gee.Collection<File>? get_raw_development_files (LibraryPhoto photo) { 883 PhotoUpdates? updates = get_existing_photo_updates (photo); 884 885 return (updates != null && updates.get_raw_developer_files () != null) ? 886 updates.get_raw_developer_files () : photo.get_raw_developer_files (); 887 } 888 889 public void update_editable_file (LibraryPhoto photo, File file) { 890 fetch_photo_updates (photo).set_editable_file (file); 891 } 892 893 public void update_editable_file_info_altered (LibraryPhoto photo) { 894 fetch_photo_updates (photo).set_editable_file_info_altered (true); 895 } 896 897 public void update_raw_development_file (LibraryPhoto photo, File file) { 898 fetch_photo_updates (photo).add_raw_developer_file (file); 899 } 900 901 public void update_raw_development_file_info_altered (LibraryPhoto photo) { 902 fetch_photo_updates (photo).set_raw_developer_file_info_altered (true); 903 } 904 905 public void update_editable_file_in_alteration (LibraryPhoto photo, bool in_alteration) { 906 fetch_photo_updates (photo).set_editable_in_alteration (in_alteration); 907 } 908 909 public void update_editable_file_alterations_completed (LibraryPhoto photo, FileInfo info) { 910 fetch_photo_updates (photo).set_editable_file_info (info); 911 fetch_photo_updates (photo).set_editable_in_alteration (false); 912 } 913 914 public void update_raw_development_file_in_alteration (LibraryPhoto photo, bool in_alteration) { 915 fetch_photo_updates (photo).set_raw_development_in_alteration (in_alteration); 916 } 917 918 public void update_raw_development_file_alterations_completed (LibraryPhoto photo) { 919 fetch_photo_updates (photo).set_raw_development_in_alteration (false); 920 } 921 922 public void update_revert_to_master (LibraryPhoto photo) { 923 fetch_photo_updates (photo).set_revert_to_master (true); 924 } 925 926 protected override void process_updates (Gee.Collection<MonitorableUpdates> all_updates, 927 TransactionController controller, ref int op_count) throws Error { 928 base.process_updates (all_updates, controller, ref op_count); 929 930 Gee.Map<LibraryPhoto, File> set_editable_file = null; 931 Gee.Map<LibraryPhoto, FileInfo> set_editable_file_info = null; 932 Gee.Map<LibraryPhoto, Gee.Collection<File>> set_raw_developer_files = null; 933 Gee.ArrayList<LibraryPhoto> revert_to_master = null; 934 Gee.ArrayList<LibraryPhoto> reimport_master = null; 935 Gee.ArrayList<LibraryPhoto> reimport_editable = null; 936 Gee.ArrayList<LibraryPhoto> reimport_raw_developments = null; 937 int reimport_job_count = 0; 938 939 foreach (MonitorableUpdates monitorable_updates in all_updates) { 940 if (op_count >= MAX_OPERATIONS_PER_CYCLE) 941 break; 942 943 PhotoUpdates? updates = monitorable_updates as PhotoUpdates; 944 if (updates == null) 945 continue; 946 947 if (updates.get_editable_file () != null) { 948 if (set_editable_file == null) 949 set_editable_file = new Gee.HashMap<LibraryPhoto, File> (); 950 951 set_editable_file.set (updates.photo, updates.get_editable_file ()); 952 updates.set_editable_file (null); 953 op_count++; 954 } 955 956 if (updates.get_editable_file_info () != null) { 957 if (set_editable_file_info == null) 958 set_editable_file_info = new Gee.HashMap<LibraryPhoto, FileInfo> (); 959 960 set_editable_file_info.set (updates.photo, updates.get_editable_file_info ()); 961 updates.set_editable_file_info (null); 962 op_count++; 963 } 964 965 if (updates.get_raw_developer_files () != null) { 966 if (set_raw_developer_files == null) 967 set_raw_developer_files = new Gee.HashMap<LibraryPhoto, Gee.Collection<File>> (); 968 969 set_raw_developer_files.set (updates.photo, updates.get_raw_developer_files ()); 970 updates.clear_raw_developer_files (); 971 op_count++; 972 } 973 974 if (updates.is_revert_to_master ()) { 975 if (revert_to_master == null) 976 revert_to_master = new Gee.ArrayList<LibraryPhoto> (); 977 978 if (revert_to_master.size < MAX_REVERTS_PER_CYCLE) { 979 revert_to_master.add (updates.photo); 980 updates.set_revert_to_master (false); 981 } 982 op_count++; 983 } 984 985 if (updates.is_reimport_master () && reimport_job_count < MAX_REIMPORT_JOBS_PER_CYCLE) { 986 if (reimport_master == null) 987 reimport_master = new Gee.ArrayList<LibraryPhoto> (); 988 989 reimport_master.add (updates.photo); 990 updates.set_reimport_master (false); 991 reimport_job_count++; 992 op_count++; 993 } 994 995 if (updates.is_reimport_editable () && reimport_job_count < MAX_REIMPORT_JOBS_PER_CYCLE) { 996 if (reimport_editable == null) 997 reimport_editable = new Gee.ArrayList<LibraryPhoto> (); 998 999 reimport_editable.add (updates.photo); 1000 updates.set_reimport_editable (false); 1001 reimport_job_count++; 1002 op_count++; 1003 } 1004 } 1005 1006 if (set_editable_file != null) { 1007 mdbg ("Changing editable file of %d photos".printf (set_editable_file.size)); 1008 1009 try { 1010 Photo.set_many_editable_file (set_editable_file); 1011 } catch (DatabaseError err) { 1012 AppWindow.database_error (err); 1013 } 1014 } 1015 1016 if (set_editable_file_info != null) { 1017 mdbg ("Updating %d editable files timestamps".printf (set_editable_file_info.size)); 1018 1019 try { 1020 Photo.update_many_editable_timestamps (set_editable_file_info); 1021 } catch (DatabaseError err) { 1022 AppWindow.database_error (err); 1023 } 1024 } 1025 1026 if (revert_to_master != null) { 1027 mdbg ("Reverting %d photos to master".printf (revert_to_master.size)); 1028 1029 foreach (LibraryPhoto photo in revert_to_master) 1030 photo.revert_to_master (); 1031 } 1032 1033 // 1034 // Now that the metadata has been updated, deal with imports and reimports 1035 // 1036 1037 if (reimport_master != null) { 1038 mdbg ("Reimporting %d masters".printf (reimport_master.size)); 1039 1040 foreach (LibraryPhoto photo in reimport_master) { 1041 assert (!master_reimport_pending.has_key (photo)); 1042 1043 ReimportMasterJob job = new ReimportMasterJob (this, photo); 1044 master_reimport_pending.set (photo, job); 1045 workers.enqueue (job); 1046 } 1047 } 1048 1049 if (reimport_editable != null) { 1050 mdbg ("Reimporting %d editables".printf (reimport_editable.size)); 1051 1052 foreach (LibraryPhoto photo in reimport_editable) { 1053 assert (!editable_reimport_pending.has_key (photo)); 1054 1055 ReimportEditableJob job = new ReimportEditableJob (this, photo); 1056 editable_reimport_pending.set (photo, job); 1057 workers.enqueue (job); 1058 } 1059 } 1060 1061 if (reimport_raw_developments != null) { 1062 mdbg ("Reimporting %d raw developments".printf (reimport_raw_developments.size)); 1063 1064 foreach (LibraryPhoto photo in reimport_raw_developments) { 1065 assert (!raw_developments_reimport_pending.has_key (photo)); 1066 1067 ReimportRawDevelopmentJob job = new ReimportRawDevelopmentJob (this, photo); 1068 raw_developments_reimport_pending.set (photo, job); 1069 workers.enqueue (job); 1070 } 1071 } 1072 } 1073 1074 private void on_master_reimported (BackgroundJob j) { 1075 ReimportMasterJob job = (ReimportMasterJob) j; 1076 1077 // no longer pending 1078 bool removed = master_reimport_pending.unset (job.photo); 1079 assert (removed); 1080 1081 if (job.err != null) { 1082 critical ("Unable to reimport %s due to master file changing: %s", job.photo.to_string (), 1083 job.err.message); 1084 1085 update_offline (job.photo); 1086 1087 return; 1088 } 1089 1090 if (!job.mark_online) { 1091 // the prepare_for_reimport_master failed, photo is now considered offline 1092 update_offline (job.photo); 1093 1094 return; 1095 } 1096 1097 try { 1098 job.photo.finish_reimport_master (job.reimport_state); 1099 } catch (DatabaseError err) { 1100 AppWindow.database_error (err); 1101 } 1102 1103 // now considered online 1104 if (job.photo.is_offline ()) 1105 update_online (job.photo); 1106 1107 mdbg ("Reimported master for %s".printf (job.photo.to_string ())); 1108 } 1109 1110 private void on_master_reimport_cancelled (BackgroundJob j) { 1111 bool removed = master_reimport_pending.unset (((ReimportMasterJob) j).photo); 1112 assert (removed); 1113 } 1114 1115 private void on_editable_reimported (BackgroundJob j) { 1116 ReimportEditableJob job = (ReimportEditableJob) j; 1117 1118 // no longer pending 1119 bool removed = editable_reimport_pending.unset (job.photo); 1120 assert (removed); 1121 1122 if (job.err != null) { 1123 critical ("Unable to reimport editable %s: %s", job.photo.to_string (), job.err.message); 1124 1125 return; 1126 } 1127 1128 try { 1129 job.photo.finish_reimport_editable (job.state); 1130 } catch (DatabaseError err) { 1131 AppWindow.database_error (err); 1132 } 1133 1134 mdbg ("Reimported editable for %s".printf (job.photo.to_string ())); 1135 } 1136 1137 private void on_editable_reimport_cancelled (BackgroundJob j) { 1138 bool removed = editable_reimport_pending.unset (((ReimportEditableJob) j).photo); 1139 assert (removed); 1140 } 1141 1142 private void on_raw_development_reimported (BackgroundJob j) { 1143 ReimportRawDevelopmentJob job = (ReimportRawDevelopmentJob) j; 1144 1145 // no longer pending 1146 bool removed = raw_developments_reimport_pending.unset (job.photo); 1147 assert (removed); 1148 1149 if (job.err != null) { 1150 critical ("Unable to reimport raw development %s: %s", job.photo.to_string (), job.err.message); 1151 1152 return; 1153 } 1154 1155 try { 1156 job.photo.finish_reimport_raw_development (job.state); 1157 } catch (DatabaseError err) { 1158 AppWindow.database_error (err); 1159 } 1160 1161 mdbg ("Reimported raw development for %s".printf (job.photo.to_string ())); 1162 } 1163 1164 private void on_raw_development_reimport_cancelled (BackgroundJob j) { 1165 bool removed = raw_developments_reimport_pending.unset (((ReimportRawDevelopmentJob) j).photo); 1166 assert (removed); 1167 } 1168} 1169