Sync & Re-Sync

One algorithm for both: compare the ids on the phone with the ids in the index, and act on the difference.

There is one algorithm. A first Sync and a Re-Sync differ only in what the index already holds: a first sync compares your photos with an empty index, so everything is added.

EVERY SYNC, FORCED OR SCHEDULED 01 Make sure the index is there Reuse it, create it, or recreate it if it vanished. The schema is checked and uploaded if missing. 02 Scan the chosen folders Through MediaStore. Every photo gets its id: md5 of its absolute path. 03 Download the index's ids q=*:*, fl=id, sort=id asc, 1000 rows per page, start advancing until a short page. 04 Delete what left the phone Ids in the index but not on the phone, deleted 500 at a time. 05 Add what is new 20 photos per round: cache or image_clip, one batch_embed, one /update. Then a commit and fresh plan usage. Only one sync ever runs at a time. Force Re-Sync while one is running tells you to be patient instead of starting another.

Figure 1 — the five steps of every sync, forced or scheduled.

01 · The steps
  1. Make sure the index is there. Reuse it, create it, or create it again if it vanished; check it carries the app's current configuration and bring it up to date if not. See the phone's index.
  2. Scan the chosen folders through MediaStore and give every photo its id, the md5 of its absolute path.
  3. Download every id the index holds, 1000 at a time: q=*:*, fl=id, sort=id asc, rows=1000, with start moving on by 1000 until a page comes back shorter. Sorting by id keeps the pages lined up.
  4. Delete every id that is in the index but no longer on the phone, 500 per request.
  5. Add every photo that is on the phone but not in the index, 20 per round (below). The same round also rewrites photos that need it: photos edited in place (same path, so the same id, but a different size or modification time than the cache remembers), photos you asked to re-sync from the grid, and photos with a position whose place words are still missing.

After the last round the app makes a hard commit, forgets cached photos that are no longer on the phone, and reads the plan usage again.

ON THE PHONE a1f3… IMG_0001.jpg b72c… IMG_0002.jpg c9d0… IMG_0003.jpg e41b… IMG_0005.jpg fresh scan of your folders IN THE INDEX a1f3… b72c… d5e6… fetched 1000 ids at a time RE-SYNC DOES a1f3… keep b72c… keep c9d0… add e41b… add d5e6… delete id = md5 of the photo's absolute path, built by one function for the phone and for the index. A first Sync is the same comparison with an empty index: everything on the phone is added.

Figure 2 — the comparison: keep what is in both, add what is only on the phone, delete what is only in the index.

02 · One round of 20 photos
  1. For each photo, take its document from the phone's cache if the file is unchanged. Otherwise read its camera details on the phone, make an upright 640 px JPEG copy, and send the copy to image_clip, which answers twelve words describing it.
  2. For the photos of the round that have a GPS position and no place yet, look the positions up in one call to nearby_places on opensolr.com and write the nearest named place into each document: city, region, province, community, country. Positions are grouped to about 10 m and remembered on the phone, so a place is asked for once.
  3. On plans with vector search, send the round's words to batch_embed in one call and get one vector per photo back.
  4. Write the round's documents to the index in one POST /update with commitWithin=10000, so they appear in search within ten seconds while the next round starts.
  5. Store each photo's document and vector in the cache, and move the progress on.
03 · The cache: a photo is paid for once

Reading a photo and embedding its words both count against the plan. The phone keeps, per photo, the document that was written and its vector, in a private SQLite database. An entry is used only while the file's size and modification time are unchanged, so an edited photo is read again, and the change is noticed at the next sync even though the photo's id did not change. Opensolr keeps its own permanent answers too: the same picture is never read by CLIP twice, and a position is never looked up twice.

What this means in practice

Empty the index from the control panel, lose it, or reinstall the app: the next sync refills the index from the cache without a single AI request. Only photos the phone has never read cost anything.

04 · When a run stops early
A photo cannot be decoded, or is refused

Skips it, counts it as skipped, carries on. It is tried again at every Re-Sync.

Per-minute or per-hour rate limit

Waits as long as the server asks and retries, up to six times.

Monthly AI requests used up

Commits what was written, stops, and posts a notification with an Upgrade button.

Index over disk space or bandwidth

Stops and posts a notification with an Upgrade button.

Plan lost vector search

Carries on without vectors.

Index password changed

Reads the new one from the account and retries.

API key refused

Stops and asks you to sign in again.

Anything else

Commits what was written and stops; the next sync continues.

A run never has to start over. The next Re-Sync compares ids again and only adds what is still missing.

05 · When the app's configuration moves on

A new version of the app can ship a newer schema.xml or solrconfig.xml (new fields, autocomplete, spellcheck). The first sync after the update notices that the index is on the older configuration, uploads the new one, and then writes every photo again from the phone's cache, without AI requests, so the new fields are filled in. Search keeps working throughout; while this runs the photo grid shows Index update in progress, and the new filters fill in as photos are written.

Opensolr Photos is open source and MIT licensed. Questions about your Opensolr account, index or plan go to opensolr.com/contact; questions about the app itself belong on GitHub.

Opensolr Photos Documentation