<feed xmlns='http://www.w3.org/2005/Atom'>
<title>android/rsend/app/src/main, branch 0.2.1</title>
<subtitle>[no description]</subtitle>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/'/>
<entry>
<title>doc: correct drift and move the build notes to the root</title>
<updated>2026-08-23T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-23T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=780d3167a427d6964806694ef5282be0c6f4cd33'/>
<id>780d3167a427d6964806694ef5282be0c6f4cd33</id>
<content type='text'>
The Rsync comment about draining a hostile remote's pipe sat on the
Tally type, where it means nothing; it describes boundedLines. Scheduler
explained itself with what happened during development instead of the
constraint that WorkManager persists unique names across upgrades, and
MainActivity and SyncLog did the same. Keys pointed at a pin() that does
not exist. The App header restated the class name and omitted half of
what onCreate does. OUT_FORMAT had no caller outside its file.

The README layout list omitted fastlane. The e2e test hardcodes a copy
of the app's rsync vector with nothing saying so. useradd already writes
a locked password field, so "skip passwd -l" does not leave a non-locked
one and key auth is still refused; verified against sshd with UsePAM no.
The CI file also runs verify-repro on a tag, and the NDK and build-tools
are pinned by revision and verified against Google's manifest rather
than a checksum this repository owns, which the reproducibility notes
claimed for every input.

metadata/ held one document and nothing else, and reads as a store
metadata directory beside fastlane/metadata. Root, extensionless,
beside THIRD_PARTY.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The Rsync comment about draining a hostile remote's pipe sat on the
Tally type, where it means nothing; it describes boundedLines. Scheduler
explained itself with what happened during development instead of the
constraint that WorkManager persists unique names across upgrades, and
MainActivity and SyncLog did the same. Keys pointed at a pin() that does
not exist. The App header restated the class name and omitted half of
what onCreate does. OUT_FORMAT had no caller outside its file.

The README layout list omitted fastlane. The e2e test hardcodes a copy
of the app's rsync vector with nothing saying so. useradd already writes
a locked password field, so "skip passwd -l" does not leave a non-locked
one and key auth is still refused; verified against sshd with UsePAM no.
The CI file also runs verify-repro on a tag, and the NDK and build-tools
are pinned by revision and verified against Google's manifest rather
than a checksum this repository owns, which the reproducibility notes
claimed for every input.

metadata/ held one document and nothing else, and reads as a store
metadata directory beside fastlane/metadata. Root, extensionless,
beside THIRD_PARTY.
</pre>
</div>
</content>
</entry>
<entry>
<title>app: remove duplicated and dead code</title>
<updated>2026-08-23T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-23T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=0c653418a67fe960bb3126559fe77833faaa385c'/>
<id>0c653418a67fe960bb3126559fe77833faaa385c</id>
<content type='text'>
upgradeLegacy assembled every remote, folder and schedule twice: once as
Remote and Folder objects to validate, and again as a parallel JSON tree
to return. Build the objects and serialise them, and drop the dead
endpointAllowed branch, the redundant pin emptiness test, and the
excludes length pre-check that excludesAllowed already makes.

atomicWrite existed verbatim in Config and Keys and was open-coded a
third time in LastSync, which then reached into Config's companion for
the bounded reader. The same bounded read was open-coded twice more, for
the encrypted key and for an imported one. Both are plain file
operations on the same private directory and belong in one place, with
readText a thin wrapper over readBytes.

Generating and importing an identity key ran the same twenty lines of
worker thread, error capture, refresh, toast and dialog; only the action
and two strings differed. rotateIfBig rotated at length &gt;= MAX_BYTES,
which line() already subsumes, and its only caller invoked it
immediately before a line(). notifyError and notifyDeleted were the same
builder twice. openSettings nested a try/catch and repeated one toast;
take the candidates as a vararg. RemoteActivity.save defaulted its
argument to current(), which validates and writes the status field, so
the default hid that side effect from one of two callers.

Dead on arrival: Outcome.deleted's default, which no caller omits; the
rsa-sha2-* arms of hostKeyFile, since rsh prints PublicKey.Type() and an
RSA host key is always ssh-rsa; and the limit &lt;= 0 branch in
SyncLog.text, which tail already handles and handled more accurately.
The config recovery note is now written with an explicit charset like
every other write here.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
upgradeLegacy assembled every remote, folder and schedule twice: once as
Remote and Folder objects to validate, and again as a parallel JSON tree
to return. Build the objects and serialise them, and drop the dead
endpointAllowed branch, the redundant pin emptiness test, and the
excludes length pre-check that excludesAllowed already makes.

atomicWrite existed verbatim in Config and Keys and was open-coded a
third time in LastSync, which then reached into Config's companion for
the bounded reader. The same bounded read was open-coded twice more, for
the encrypted key and for an imported one. Both are plain file
operations on the same private directory and belong in one place, with
readText a thin wrapper over readBytes.

Generating and importing an identity key ran the same twenty lines of
worker thread, error capture, refresh, toast and dialog; only the action
and two strings differed. rotateIfBig rotated at length &gt;= MAX_BYTES,
which line() already subsumes, and its only caller invoked it
immediately before a line(). notifyError and notifyDeleted were the same
builder twice. openSettings nested a try/catch and repeated one toast;
take the candidates as a vararg. RemoteActivity.save defaulted its
argument to current(), which validates and writes the status field, so
the default hid that side effect from one of two callers.

Dead on arrival: Outcome.deleted's default, which no caller omits; the
rsa-sha2-* arms of hostKeyFile, since rsh prints PublicKey.Type() and an
RSA host key is always ssh-rsa; and the limit &lt;= 0 branch in
SyncLog.text, which tail already handles and handled more accurately.
The config recovery note is now written with an explicit charset like
every other write here.
</pre>
</div>
</content>
</entry>
<entry>
<title>app: rename RsyncRunner to Rsync</title>
<updated>2026-08-23T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-23T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=dc0338f8c0c9e74b277169d812c432f7ea4888e3'/>
<id>dc0338f8c0c9e74b277169d812c432f7ea4888e3</id>
<content type='text'>
Runner names the architecture rather than the thing. The object is the
rsync invocation.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Runner names the architecture rather than the thing. The object is the
rsync invocation.
</pre>
</div>
</content>
</entry>
<entry>
<title>app: apply the schedule when folders change</title>
<updated>2026-08-23T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-23T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=f6706d04bd347465f9db1e484007db215100d965'/>
<id>f6706d04bd347465f9db1e484007db215100d965</id>
<content type='text'>
Scheduler.apply cancels the periodic job while no folder points at a
pinned remote and enqueues it once one does, so adding the first folder
or removing the last one changes what should be scheduled. The remote
and schedule editors already re-applied on save; this one did not, and a
user who left the app from this screen kept a job that could only fire
and refuse.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Scheduler.apply cancels the periodic job while no folder points at a
pinned remote and enqueues it once one does, so adding the first folder
or removing the last one changes what should be scheduled. The remote
and schedule editors already re-applied on save; this one did not, and a
user who left the app from this screen kept a job that could only fire
and refuse.
</pre>
</div>
</content>
</entry>
<entry>
<title>app: keep editor state across recreation</title>
<updated>2026-08-23T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-23T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=27bce144aa808bad16435f35132bc08da2b05bf4'/>
<id>27bce144aa808bad16435f35132bc08da2b05bf4</id>
<content type='text'>
Browsing restarted at the storage root on every configuration change.
The restored path goes through safeDirectory like a typed one, so it
still cannot escape the root.

A pinned remote wrote its index into both the instance state and the
intent. The instance state already survives recreation.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Browsing restarted at the storage root on every configuration change.
The restored path goes through safeDirectory like a typed one, so it
still cannot escape the root.

A pinned remote wrote its index into both the instance state and the
intent. The instance state already survives recreation.
</pre>
</div>
</content>
</entry>
<entry>
<title>app: read the dashboard off the main thread</title>
<updated>2026-08-23T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-23T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=ca9037ccf3030df287e52b1fa92ab7eb0871eec2'/>
<id>ca9037ccf3030df287e52b1fa92ab7eb0871eec2</id>
<content type='text'>
Every value the dashboard draws comes from an app-private file, and it
read all of them during layout, on every resume and at the end of every
sync. Scheduler.apply did the same at process start, where it also opens
WorkManager's database. Read on a worker thread and draw the result;
overlapping refreshes are settled by generation, the same way the log
viewer already settles its own.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Every value the dashboard draws comes from an app-private file, and it
read all of them during layout, on every resume and at the end of every
sync. Scheduler.apply did the same at process start, where it also opens
WorkManager's database. Read on a worker thread and draw the result;
overlapping refreshes are settled by generation, the same way the log
viewer already settles its own.
</pre>
</div>
</content>
</entry>
<entry>
<title>app: report config limits instead of crashing</title>
<updated>2026-08-23T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-23T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=e8c6c4ebb3d76ee280043bb66cf499584c591310'/>
<id>e8c6c4ebb3d76ee280043bb66cf499584c591310</id>
<content type='text'>
The remote and folder counts and the serialized config size are enforced
only inside save, which throws. Every other rule an editor can break is
reported in the form, so a config that outgrew a limit crashed the
activity instead of saying so. overLimit answers before the entry is
added, and the editors report the answer.

hostKeyAllowed reconstructs the known_hosts address that rsh writes with
knownhosts.Normalize, and nothing recorded or tested that coupling.
Normalize leaves the host bare on port 22 for IPv6 literals too, so
bracketing them here looks right and would reject every IPv6 pin.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The remote and folder counts and the serialized config size are enforced
only inside save, which throws. Every other rule an editor can break is
reported in the form, so a config that outgrew a limit crashed the
activity instead of saying so. overLimit answers before the entry is
added, and the editors report the answer.

hostKeyAllowed reconstructs the known_hosts address that rsh writes with
knownhosts.Normalize, and nothing recorded or tested that coupling.
Normalize leaves the host bare on port 22 for IPv6 literals too, so
bracketing them here looks right and would reject every IPv6 pin.
</pre>
</div>
</content>
</entry>
<entry>
<title>build: pin and verify release inputs</title>
<updated>2026-08-16T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-16T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=8c6390571d28ff21a98ed3802458aceddbb6d2a0'/>
<id>8c6390571d28ff21a98ed3802458aceddbb6d2a0</id>
<content type='text'>
Support Alpine/musl and Debian/glibc hosts, lock downloaded and
Gradle artifacts, and verify native and APK security properties.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Support Alpine/musl and Debian/glibc hosts, lock downloaded and
Gradle artifacts, and verify native and APK security properties.
</pre>
</div>
</content>
</entry>
<entry>
<title>app: harden backup execution</title>
<updated>2026-08-16T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-16T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=0e8a87f2ad2bd40d34996374aebb6dff070e4b5f'/>
<id>0e8a87f2ad2bd40d34996374aebb6dff070e4b5f</id>
<content type='text'>
Validate persisted state and destination boundaries, make interruption
and mirror deletion explicit, and keep scheduled work singular.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Validate persisted state and destination boundaries, make interruption
and mirror deletion explicit, and keep scheduled work singular.
</pre>
</div>
</content>
</entry>
<entry>
<title>app: support multiple named remotes</title>
<updated>2026-08-16T00:00:00+00:00</updated>
<author>
<name>Lena</name>
<email>lena@omega</email>
</author>
<published>2026-08-16T00:00:00+00:00</published>
<link rel='alternate' type='text/html' href='http://git.0xlena.dev/android/rsend/commit/?id=0890d139e5fa501561e469aca7854791a936fcb6'/>
<id>0890d139e5fa501561e469aca7854791a936fcb6</id>
<content type='text'>
Let each folder select a named SSH endpoint and migrate the published
0.1.x configuration without discarding its folders or host pin.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Let each folder select a named SSH endpoint and migrate the published
0.1.x configuration without discarding its folders or host pin.
</pre>
</div>
</content>
</entry>
</feed>
