Repository navigation
Releases: Salcedoandrew/freefilesync-offload
Release list
v0.10.3
The README now carries the upstream feature list, and About Offload has the email.
Added
- A new "Upstream features" section in the README: all 76 things FreeFileSync 14.12 does, each with a stable ID, where it lives in the binary, its config keys and how strongly the claim is evidenced, plus a worksheet for comparing another app against it. Read out of the shipped binary by static analysis, so the fork can be compared against upstream without re-running the analysis.
- The About panel now shows gcastroandrew@gmail.com, as a mailto link under the website.
Fixed
- The Credits.rtf added with the About box never displayed: the panel's credits are built in code (Entry.swift), which overrides the bundle file. It is removed, and the Makefile no longer copies it.
v0.10.2
About Offload says what the app is, and who made it.
Added
- The About panel was the stock one, so it said "Offload" and a version number and
stopped there. It now carries the author, a link to heartfeltmedia.co, the sentence
the README leads with — copies to every destination at once, hashes every byte, reads
each copy back, leaves a report you can check a year later — and a copyright line.
Notes
- The colours are the system label and link colours, so the panel reads correctly if it
is ever shown in light mode; the window itself stays dark.
v0.10.1
The reports stop naming a version that is three releases old.
Fixed
report.json,report.csvand the MHL each carry the engine's version, and the
engine had0.7.0typed into it as a constant. Every report this fork has written
since 0.7.0 therefore named the wrong version — the kind of thing nobody notices
until they open one a year later and try to match it to a release. The version now
comes from the build, read out of the bundle'sInfo.plist, which is already the
single source of truth the release workflow reads. One version bump, one place, and
no constant left to remember.
Notes
- The MHL
<version>element and the JSON"tool": "ffsoffload <version>"field
both change with this release. Anything keyed on the old string now sees the real
version instead of a fossil. - A build that cannot read the plist stamps
0.0.0-unknownrather than inventing a
plausible-looking version.
v0.10.0
A card can be copied without its proxies, and filed where you will look for it.
Added
- Skip patterns. A Skip field in the Transfers panel takes patterns like
*.LRV | *.THM | MISC/, and the engine applies them inside the one walk that plans
the copy — so the fit check beside each destination counts what will actually be
copied, not what the card is holding.*and?are wildcards, a trailing/
prunes a whole folder, and matching ignores case, because cards mixIMG_1.THMand
img_1.thmfreely. On the command line it is--exclude. - A Subfolder field with date macros.
%Date%(2026-10-26),%Year%,%Month%,
%Day%and%Card%, with/nesting folders, so a shoot files itself under
2026/10/26without being typed. Blank keeps the card's own name, which is what it
did before the field existed. - A fuller
--jsonsummary. It now carries the totals a script should not have to
add up itself: verified, written, skipped, mismatch, failed, broken, bytes, the
report path, the verify mode and hash, and the skip patterns that shaped the run. - An app icon, so the app is not a blank page in the Dock and in Finder.
Notes
- A Skip field that matches every file is refused rather than run. "Copied 0 files,
OK" is exactly how a card gets formatted for nothing, so the job fails by name. - The patterns are captured when a row is queued, not when Transfer is pressed:
editing the field afterwards cannot change a plan already pressed Add on, and the
report records the patterns the run really used. - Typing in Skip re-walks the sources once the typing stops, so the fit check follows
the field instead of answering a question the next run will not ask. - The matcher has no wx or glib dependency, so it builds and runs on its own:
make -f Makefile.macos filter-testruns 29 checks in about a second, and CI runs
them. The engine suite gained a Skip section that drives the real job, including
that the fit check and the copy agree file for file. - The updater's feed decision moved out of
performCheckintoUpdater.decision, and
is now covered directly instead of being assumed: an 0.8.1 copy is offered the
feed's 0.9.0 with its archive and checksum, a current copy is told it is current, a
feed that lags behind never offers a downgrade, and 0.10.0 beats 0.9.0.
v0.9.0
A finished run can be swept out of the window in one click.
Added
- Clear Finished. Completed rows used to sit in the Transfers list for the life of the
window, one cross each. A small Clear Finished button now appears in the Transfers
header once anything is over, and removes the rows that completed or were cancelled.
A failed row stays behind on purpose: its text is the only place that names which files
failed, so a button that quietly erased that after a bad card would be the wrong
default; the row's own cross still clears it.
Notes
- The mouse test now covers the queue and the clear: a queued row has no job to pause, a
run started under the Pause switch comes up paused, and Clear Finished drops the
cancelled row while keeping the paused one.
v0.8.1
Documentation only: no engine or window code changed in this release.
Fixed
- The release table was four versions behind. It stopped at v0.4.0, so 0.5.0 through
0.8.0 were missing from the one place the README lists the releases; each row now links
its own tag. - Two dates disagreed between that table and this file. Both now date a release by the
local day it went out, and say so, because GitHub stamps releases in UTC and pushes an
evening release onto the following day.
v0.8.0
Add queues the copies, Transfer starts them, and the run ends with a receipt that says
what moved and how fast.
Added
- A Transfer button. Add now queues rows as
Ready, with no engine job behind them;
the footer grows a Transfer button that starts them and disappears once nothing is
waiting. It takes the accent while rows are queued, with Add stepping back to a neutral
fill, so there is only ever one obvious thing to press. - A confirmation window. When every row a run started has finished, a sheet reports
the files and bytes moved, the rate the run sustained, and one line per destination
with its own count, rate and report folder. The rate is bytes over the engine's own
elapsed time, and the run's duration is its slowest row because rows copy side by side.
Damaged files turn the headline amber and are counted separately. --render summarydraws that window offscreen with a sample run, so it can be
screenshotted without copying anything.
Fixed
- The download's own instructions named the wrong version.
HOW-TO-OPEN.txtwas
copied verbatim into the archive, so the 0.7.0 zip shipped a note whose header,
checksum command and limitations heading all still said 0.4.0. The note now carries
@VERSION@and@ARCH@placeholders thatmake zipfills fromInfo.plistand
uname, and the build refuses to package a note that does not name the archive it is
sitting in.
Notes
- A queued row has no Pause button, because there is no job to hold back; cancelling one
retires the plan instead of leaving it to start on the next Transfer press. The
updater's "transfers are running" check ignores queued rows, since nothing is written. - A release is now cut only after CI goes green, and a run whose newest commit is not the
version bump is refused rather than published without those commits.
v0.7.0
A damaged photo now fails the run instead of disappearing into it, the updater cannot
install a release it cannot check, and every transfer row says how fast it is going and
how much has moved.
Added
- Transfer stats. Each row reports bytes/sec, files/sec,
n of m files, bytes done
of total and the ETA while it runs, then the counts and elapsed time when it finishes.
Pause/resume is a labelled Pause/Continue pill instead of a bare glyph, and a
paused row drops the rate and the ETA rather than leaving frozen numbers on screen. - A test that drives the updater's checksum gate in both directions: a release with no
sums asset is refused, one with a sums asset passes and hands its file back.
Fixed
- A damaged source file no longer lets the job claim success. It is still copied
faithfully and still carries a plain-language reason in the report, butallVerified()
now returns false when a destination counted a damaged file: the CLI says how many
were damaged and exits 1, and the window names them instead of blaming the source scan.
The exit code is what a script checks before reformatting a card, so one corrupt JPEG
now stops that. - A truncated report reads as incomplete.
DestReportcarriesexpectedFiles, pinned
from the source count before any worker starts, andallVerified()requires exactly
that many records — a destination that dies mid-run no longer looks finished. Covered
by the newtestTruncatedReport. - The updater refuses to install without a checksum. A release that published no
SHA256SUMS.txtused to skip verification entirely and install anyway; it is now
refused withchecksumMissing, and the checksum asset's HTTP status is checked
instead of ignored. - The C ABI no longer hands Swift pointers into live strings.
offload_job_current_file,
offload_job_errorandoffload_job_report_csvcopy into a per-thread buffer while the
lock is held; the worker reassigningcurrentFilecould previously free the buffer while
Swift was reading it.
Notes
- The app's self-test fixture now writes a real CR3 header in front of its random payload.
Random bytes under a.CR3name are a damaged file, and that fixture exists to exercise
the transfer path. --allow-brokenis deliberately not here: a card known to hold a bad file can only be
recorded as failed for now.
v0.6.0
A hash cache, so re-checking a card does not re-hash everything, and a
broken-file check, so a corrupt photo is flagged instead of silently filed.
Added
- Hash cache. Each destination now keeps a
.ffsoffload-cachefile at its
root: the hash of every file verified there, keyed by file name, size and
modification date, with the algorithm recorded alongside. A second run
skips what is already there and confirms it from the cache instead of
trusting name, size and date alone.--reverifynever takes a hash from
the cache: it always re-hashes every file, so a content change that keeps
size and date is still caught, and a mismatch drops the stale entry so it
can never be trusted again. The cache format is versioned, so a future
format change never silently trusts old entries, and the cache file itself
is never copied as data. A cache that cannot be saved is a warning, never
a failure: the next run just re-hashes. - Broken-file check. During the source scan, photo and RAW files are
judged at the header level only — JPEG (SOI marker and the marker after it),
PNG (signature, IHDR, and the IEND chunk at the end), TIFF (byte order,
magic 42, first directory inside the file), CR3 (ftypbox brandedcrx),
NEF and ARW (TIFF headers) — so the scan stays fast with no full decode.
A file that fails is flaggedbrokenin the CSV/JSON report with a
plain-language reason and a warning on the terminal, and it is never counted
as verified-good. It is still copied faithfully and hash-verified like any
other file: losing a corrupt photo would be worse than copying it. - The summary line now shows the broken count and the cache hits, and the
per-file report gainsbroken,broken_reasonandhash_confirmed
columns; the JSON report gains abrokencount and cache hit/miss counts.
Notes
- Same size and date, different bytes, is invisible to the cache — but not
to--reverify. If a file on a destination changes content without its
size or modification date moving, the already-present check still confirms
it from the cache: that is the same trust Duplicate Detect already places
in the date, and the price of not re-hashing every file on every run.
--reverifyalways re-hashes, so it catches the change and drops the
stale entry; delete.ffsoffload-cacheon a destination to force a full
re-hash of the already-present check too. - A dry run reads the cache but never writes one.
v0.5.0
Check for Updates, and an update that actually replaces the app.
Added
- A menu bar. There was none, so
⌘Qdid nothing and there was nowhere to put this.
About, Check for Updates…, Hide, Quit. Updater.swift: readsreleases/latest, compares versions component-wise
(0.10.0>0.9.0,1.0==1.0.0), downloads the release zip and verifies it against
the release'sSHA256SUMS.txt, unpacks it withditto, and requires the bundle id, the
tagged version and a passingcodesign --verify --deep --strictbefore going further.UpdateInstaller: the swap script waits for the app to quit, moves the old bundle aside,
moves the new one in, re-verifies it, deletes the old bundle, clears quarantine and
reopens. On any failure it restores the old bundle and reopens that. One line per attempt
in~/Library/Application Support/Offload/update/last-swap.log; the script deletes itself.- Refusals: transfers still running, a Gatekeeper-translocated copy (read-only), and a folder
the user cannot write to. - Footer chip with the version to install and download progress;
--check-update,
--update-testand--update-selftest <feed>for the terminal. make zipnow writesSHA256SUMS.txt, which is what the updater checks against.
Fixed
- A failed source scan no longer reports "Completed". The engine used to swallow a
scan error and hand the window a report that looked like success; the window now fails
the transfer and shows the reason, and the CLI prints it too. The same hole in
--self-testis fixed. - Copies now publish atomically. Each file is written to a temporary
.ffs_tmpname
and renamed into place only once its hash is verified, so a crash mid-copy can no
longer leave a partial file at its real name. Stale temp files are cleaned up at the
start of a run and skipped by the scanner.
Verified
38/38 update checks pass(--update-test): version ordering incl.0.10.0>0.9.0and
1.0==1.0.0; feed decoding; pre-release and zip-less releases refused;shasumoutput
parsed; SHA-256 equal toshasum -a 256for a 3 MB file; the script's own contents; a real
swap of two bundles on disk; and a swap of a deliberately unsigned bundle that restores
the old version and leaves an app in place.- End to end against a local feed: 0.4.0 → 0.4.1, 6.6 MB, checksum matched, staged, spawned,
swapped. The replaced bundle is 0.4.1, the old one is gone,codesign --verifypasses, and
it runs:11/11 fit checkswith 22 libraries loading from its ownContents/Frameworks. - A zip with one byte appended was refused:
checksum mismatch … Nothing was replaced, exit 3,
installed version still 0.4.0, no backup left behind. - Live check against GitHub:
current 0.4.0, latest 0.4.0 — up to date.
Notes
- Integrity, not authenticity. Ad-hoc signing gives the updater nothing to verify identity
against, so a compromised release would pass. Notarization or a signed appcast is the fix. - v0.4.0 has no updater, so its upgrade is manual; 0.5.0 onwards can update itself.
- Untested: a swap interrupted by a power cut (the script only restores on its own failures),
and an install owned by another admin account (refused by the writability check).