Engineering

Making Photo Imports Resumable on iOS

A large photo import has to survive ordinary phone use. The work behind Mapsake's checkpoints, cancellation, and retry handling starts with keeping scanned evidence separate from a saved travel record.

Разработано компанией. 7 minute read

Updated

Mapsake Record screen with photo and travel-history import sources

A photo import rarely gets the phone to itself

A large photo library is an awkward workload for a mobile app. Some images are on the device, others may need iCloud, and the person holding the phone has other things to do. They switch apps, lock the screen, change the selected album, or cancel because the scan is taking longer than expected.

The easy implementation assumes the import runs from beginning to end. Read the photos, build a list, resolve the coordinates, and save the map. That works until the process stops halfway through. Then a second attempt can repeat work, lose track of what was read, or mistake an incomplete result for the user's complete library.

Mapsake's import work separates three questions: what has been read, what can be resumed, and what has actually become part of the travel record. Keeping those answers separate matters more than making the progress bar move smoothly.

Reading a photo is not the same as saving a visit

The import pipeline starts with source metadata. For Apple Photos, that includes stable asset identifiers and available dates and coordinates. Those coordinates still need to be resolved into countries, regions, and cities, and the person importing them needs a chance to review the result.

A useful outline is:

  1. Identify the source and enumerate the available items.
  2. Read metadata in manageable batches.
  3. Save enough progress to resume the metadata work.
  4. Resolve the collected evidence into places.
  5. Review and confirm the import.
  6. Save the resulting map changes.

Finishing step three does not mean step six succeeded. A library can be fully scanned while its proposed places remain unconfirmed. A map save can fail after metadata has already been persisted. The bookkeeping needs to describe those states without turning any of them into a false success.

This also preserves an important product boundary. Looking through someone's photo metadata should not, by itself, make every inferred location a permanent visit.

Small batch files and one manifest

Mapsake uses device-local checkpoints for Apple Photos and Immich imports. Each checkpoint has a small manifest describing the source, processed count, total, batch filenames, and source-specific progress such as the next Immich page.

The metadata lives in separate batch files. Completed batches are immutable: a new batch creates a new file instead of rewriting all the metadata collected so far. The manifest is then replaced atomically to include that filename.

The order is deliberate:

write the new batch to its own file
add its filename to the manifest
atomically replace the manifest

If the process stops after the batch write but before the manifest update, the previous manifest remains the description of committed progress. The extra file is not automatically treated as a completed batch merely because it exists in the directory.

This avoids repeatedly encoding an ever-growing JSON document. The manifest grows with the number of batches; each metadata write covers only the new batch. It also gives recovery a small, explicit list of files to read.

Atomic replacement is useful, but it is not a backup system or a promise that writes cannot fail. A device can run out of storage, files can become unreadable, and the original library can change. The checkpoint is a way to recover useful work after interruption, not a replacement for preserving the source photos or exporting the finished travel record.

Remember the items that produced no metadata

A subtle resume problem appears when a photo was examined but did not produce usable metadata. If progress is reconstructed only from successful results, that item looks unprocessed. Every retry can return to the same unreadable or unlocated files.

The photo checkpoint therefore records processed asset identifiers as well as metadata. Successful identifiers already exist as keys in the metadata dictionary, so only the additional identifiers need their own list. Recovery combines both to reconstruct the set of items already examined.

That distinction makes the counters more honest. “Processed” describes the work attempted in this scan. “Located” describes the useful evidence found. Neither number claims that every photo must have GPS coordinates.

The checkpoint also belongs to a particular source identity. An Immich import from one configured source must not pick up the pagination state of another. Changing the source cannot safely mean continuing from an arbitrary page number left by the previous run.

Cancellation has to reach the worker

Saving checkpoints solves only part of the problem. The work also needs to stop when its owner stops.

An audit found that some photo work ran in detached tasks. Cancelling the foreground operation did not necessarily cancel those workers. A cancellation check inside the worker could look reassuring while checking a task that had never received the cancellation.

The repair gave off-main scan, resolution, derivation, and indexing work structured ownership. Cancellation from the parent operation reaches the work it started. The background execution expiration path cancels the sync operation before ending its execution allowance, rather than ending the allowance while work carries on independently.

There is still cooperation involved. A cancellation request does not instantly interrupt every framework call. The surrounding loops and boundaries must observe it and reject a result that is no longer eligible to be saved.

For Mapsake, this means a cancelled scan cannot simply return the partial list collected so far and let the caller interpret it as a completed enumeration.

An incomplete scan is not evidence of deletion

This is the most consequential boundary in the pipeline. Suppose a library previously contained 40,000 known items and an interrupted scan returns only the first 8,000. Treating that partial result as the complete library would make the remaining 32,000 appear to have disappeared.

Enumeration is therefore read-only. Pruning and the full-rescan completion marker wait until metadata reading has completed successfully. Results from an obsolete album scope are rejected as well: changing the source selection while work is running must not let the old selection arrive later and replace the new one.

The same principle applies to manual edits. An import is adding and reconciling source evidence. It is not permission to discard a person's notes, manually added places, or corrections because a background operation stopped early.

Those records are part of the test fixtures, not just a sentence in the interface. A retry has to leave the independent travel history intact.

Save completion last

There is another interruption window after metadata is persisted but before the derived map is saved. If the app advances its “last synced” timestamp too early, the next run may believe that the whole update succeeded and skip the unfinished work.

Mapsake advances photo watermarks and source sync timestamps only after a successful map save. A durable retry receipt covers the gap between metadata persistence and map derivation. The receipt records that work remains; it does not need to contain another copy of the person's travel data.

This is different from the import checkpoint. The checkpoint helps resume reading a source. The retry receipt helps finish applying work that has already crossed a later persistence boundary. Combining them into one generic “done” flag would lose that distinction.

Test the interruptions, not just the happy path

The useful tests start and stop work at inconvenient moments: before a worker begins, during enumeration, after a scope changes, after metadata persistence, and around a failed or interrupted save. They check both what happened and what must not have changed.

The recent cancellation work includes pre-cancelled and running workers, obsolete-scope rejection, late background expiration, and retry receipts across interrupted saves. Manual records and metadata fixtures are compared for preservation.

One stress fixture exercises 100,000 cancellation leases. That is a test of task-lifetime bookkeeping, not a claim that 100,000 real photos were imported in the same time. Separate Release simulator journeys exercise Photos permission, a sample library, backgrounding, and relaunch. Each kind of evidence answers a different question.

Progress should survive ordinary phone use

A resumable import should make interruption less expensive without making its promises larger than its evidence. The source can still be unavailable. A permission change can still require attention. A person still needs to review what the app found.

The durable improvement is that these events have distinct meanings. A scanned photo is not yet a saved visit. A checkpoint is not a completed import. An incomplete enumeration is not a deletion request. A progress timestamp is not proof that a map save succeeded.

Once those boundaries are explicit, the interface can be much simpler: show the work, preserve useful progress, stop when asked, and keep the existing travel record safe while the next attempt finishes.

Иконка приложения Mapsake

Независимый разработчик Mapsake, рассказывающий о разработке продукта, картографии, конфиденциальности и работе с платформой Apple.

Создайте свою собственную карту путешествий.

Начните с истории ваших путешествий, которая уже у вас есть.

Mapsake бесплатна, не требует учетной записи Mapsake для основных функций и сохраняет сопоставление фотографий на вашем устройстве.

Скачать Mapsake бесплатно.