For developers

DRL-FileSync Connect

Let your app keep its files in your users' own storage, on their own PC, with their permission. No server to run, no keys to manage, nothing to pay for.

Early release (0.1). Android apps: a Kotlin and Java library and a Flutter package. A JavaScript version for web services and a simulator are on the way. Everything on this page works today.

How it works

Your app asks the person's DRL-FileSync app for a folder of its own. The person taps Allow, and your app can then read, write and delete files in that folder, and nothing else. Their files stay on their own PC.

  1. Your app opens DRL-FileSync with its name. DRL-FileSync shows: “Let ‘My Notes’ use your DRL-FileSync storage?” with what the app will and will not be able to do.
  2. The person taps Allow. DRL-FileSync creates a folder on their PC for your app (5 GB by default) and sends them back to your app.
  3. Your app saves and reads files. DRL-FileSync carries each request to the PC, on the home Wi-Fi or over the internet.
  4. The person can remove your app at any time in DRL-FileSync, under Settings, DRL-FileSync Connect. Your app is told, and stops working until they allow it again.

You do not write any login or key code. The library makes your app's key, signs every request, handles the hand-over to DRL-FileSync and the way back, and remembers the connection. Every request is signed by your app and checked by the PC, so DRL-FileSync itself cannot read or change your app's files by pretending to be you.

What your users need: the DRL-FileSync app on their phone, signed in and paired with their PC. If it is not installed, the library tells your app so, and you can show a button that takes them to the download.

Quickstart: Flutter in five minutes

You need Flutter 3.24 or newer and an Android phone with the DRL-FileSync app.

1. Add the package

Download the SDK (270 KB) Unzip it next to your project (it holds the Flutter package with a small example, the Android library with a sample, and the DRL-FileSync Notes sample), then point your project at it. It will also be published on pub.dev.

pubspec.yaml
dependencies:
  filesync_connect:
    path: ../filesync_connect

That is all the setup. There is nothing to add to your Android files: the package brings its own return screen and permissions.

2. Start it when your app starts

main.dart
import 'package:filesync_connect/filesync_connect.dart';

final fileSync = FileSyncConnect(appName: 'My Notes');   // the name people see in DRL-FileSync

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await fileSync.start();   // remembers an earlier connection and listens for DRL-FileSync's answer
  runApp(const MyApp());
}

3. A button that connects

a screen
ListenableBuilder(
  listenable: fileSync,   // rebuilds whenever the state changes
  builder: (context, _) => Column(children: [
    Text(switch (fileSync.state) {
      ConnectState.notConnected => 'Not connected',
      ConnectState.waiting => 'Waiting for you to answer in DRL-FileSync...',
      ConnectState.connected => 'Connected',
    }),
    if (fileSync.message != null) Text(fileSync.message!),   // written for the person to read
    if (!fileSync.isConnected)
      FilledButton(onPressed: fileSync.connect, child: const Text('Back up with DRL-FileSync')),
    if (fileSync.needsFileSyncApp)   // the DRL-FileSync app is not on this phone
      TextButton(onPressed: fileSync.openInstallPage, child: const Text('Get the DRL-FileSync app')),
  ]),
)

Tapping the button opens DRL-FileSync, the person taps Allow, and your app comes back to the front, connected. If your app was closed meanwhile, it picks the answer up the next time it opens.

4. Save and read files

anywhere in your app
final folder = fileSync.folder;                 // your app's own folder; only when connected
await folder.mkdirs('/notes');
await folder.writeString('/notes/today.txt', 'Buy milk');
final text = await folder.readString('/notes/today.txt');
final files = await folder.list('/notes');      // names, sizes, folder or file
await folder.delete('/notes/today.txt');

5. Handle problems

anywhere in your app
try {
  await fileSync.folder.writeString('/a.txt', 'hi');
} on FileSyncException catch (e) {
  showMessage(e.message);   // for example "Your PC is off."  See the errors table below
}

Good habits. Look at folder.spaceLeft() before a big upload. Keep your own copy of the data on the phone and treat DRL-FileSync as the backup: the PC may be off. DRL-FileSync Notes does exactly that.

Android: Kotlin or Java

The same five steps, with a native library. You need Android 7 (API 24) or newer and the DRL-FileSync app on the phone.

1. Add the library

Unzip the SDK download next to your project, then add the library module to your settings.gradle.kts and use it in your app. It will also be published on Maven Central.

Gradle
// settings.gradle.kts
include(":filesync-connect")
project(":filesync-connect").projectDir = file("../filesync_connect_android/filesync-connect")

// app/build.gradle.kts
dependencies {
    implementation(project(":filesync-connect"))
}

Nothing to add to your AndroidManifest.xml: the library brings its own permissions and its own return screen.

2. Start it, and add a button

Kotlin
class MainActivity : Activity() {
    private lateinit var fileSync: FileSyncConnect

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        fileSync = FileSyncConnect(applicationContext, "My Notes")   // the name people see in DRL-FileSync
        fileSync.addListener { showState() }                         // called on the main thread whenever something changes
        fileSync.start()                                             // once: remembers an earlier connection, hears DRL-FileSync's answer
        findViewById<Button>(R.id.connect).setOnClickListener { fileSync.connect() }
    }

    private fun showState() {
        when (fileSync.state) {
            ConnectState.NOT_CONNECTED -> status.text = "Not connected"
            ConnectState.WAITING -> status.text = "Waiting for you to answer in DRL-FileSync..."
            ConnectState.CONNECTED -> status.text = "Connected"
        }
        fileSync.message?.let { messageView.text = it }              // written for the person to read
        installButton.visibility = if (fileSync.needsFileSyncApp) View.VISIBLE else View.GONE   // button: fileSync.openInstallPage()
    }
}

Tapping the button opens DRL-FileSync, the person taps Allow, and your app comes back to the front, connected. If your app was closed meanwhile, it picks the answer up the next time it starts.

3. Save and read files (on a background thread)

Folder calls talk to the PC, so never call them on the main thread.

Kotlin
thread {
    try {
        val folder = fileSync.folder                       // your app's own folder; only when connected
        folder.mkdirs("/notes")
        folder.writeString("/notes/today.txt", "Buy milk")
        val text = folder.readString("/notes/today.txt")
        val files = folder.list("/notes")                  // name, size, isDir
        folder.delete("/notes/today.txt")
    } catch (e: FileSyncException) {
        runOnUiThread { showMessage(e.message) }           // for example "Your PC is off."
    }
}

From Java the same calls work: fileSync.getFolder().writeString("/a.txt", "hi"), inside an executor or thread.

Keys and privacy

Your app's key lives in the Android Keystore. It can sign, but it can never be read out, not even by your own app, and it is deleted when your app is uninstalled. The library never asks for storage permissions, never needs the internet permission, and only sees its own folder.

Testing without a phone

a unit test
val fileSync = FileSyncConnect.connectedTo(FakeFolder())   // already connected, files kept in memory
fileSync.folder.mkdirs("/notes")
fileSync.folder.writeString("/notes/a.txt", "hi")

The SDK download has a small Kotlin sample app (sample) with this whole flow in one file.

Other technologies and how it works underneath

Anything that can run on Android can talk to DRL-FileSync: React Native, Unity, Xamarin, Kotlin Multiplatform. You need four things.

1. A key for your app

Make an ECDSA P-256 key pair once and keep the private key in your app. Its identity on the PC is the key id: the lower-case hex SHA-256 of the public key in SubjectPublicKeyInfo (DER) form. There is no developer account or approval.

2. Ask DRL-FileSync (an Android intent)

start an ACTION_VIEW intent with this address
filesync://connect
  ?name=My%20Notes                       the name people see (40 characters at most)
  &key=<base64url of the public key>     SubjectPublicKeyInfo, no padding
  &return=filesyncconnect://<your app id>/return      where to send the person afterwards
  &state=<a random string you made>      comes back unchanged, so you know the answer is for this request

DRL-FileSync answers by opening your return link with state, ok=1 (or ok=0&error=denied), key_id and folder. Check that state matches, and keep your private key. If starting the intent finds no activity, DRL-FileSync is not installed. Declare the filesync scheme in your manifest <queries> so Android lets you see it.

3. Hand each request to DRL-FileSync

Your app does not talk to the PC. It signs a request and passes it to DRL-FileSync, which carries it. Call the content provider com.thedrlinitiative.filesync.connect:

Kotlin
contentResolver.call(Uri.parse("content://com.thedrlinitiative.filesync.connect"), "request", null, Bundle().apply {
    putString("method", "PUT")                   // GET, PUT, POST or DELETE
    putString("target", "/v1/app/drive/file?path=%2Fnotes%2Fa.txt")
    putBundle("headers", signedHeaders)          // see below
    putByteArray("body", bytes)                  // up to about 600 KB per request
})
// answer: status (Int, 0 = DRL-FileSync could not reach the PC), body (bytes), type, error (a sentence for the person)

4. Sign every request

The headers are X-FS-Version: 1, X-Key (your key id), X-Time (Unix seconds), X-Nonce (random, used once) and X-Sig: the base64 of the 64 raw bytes (r, then s) of an ECDSA P-256 / SHA-256 signature over this text:

what you sign (\n is a line break)
1\n<METHOD>\n<target exactly as sent>\n<X-Time>\n<X-Nonce>\n<hex SHA-256 of the body (empty body: SHA-256 of nothing)>

For PUT also send X-Content-Sha256 (the same body hash), X-Total-Size (bytes) and X-Offset: 0.

The calls

CallWhat it does
GET /v1/app/spaceSpace used and left for your app.
GET /v1/app/drive?path=/List a folder.
GET /v1/app/drive/file?path=/a.txtRead a file.
PUT /v1/app/drive/file?path=/a.txtStore a file (the PC keeps the old one and picks a new name if it exists; delete first to replace).
DELETE /v1/app/drive/file?path=/a.txtDelete a file for good (an app has no Bin).
POST /v1/app/drive/folder?path=/notesMake a folder.
POST /v1/app/drive/rename, copy, moveWith path and name or to.

Paths are always inside your app's own folder. The Flutter package in the download is a complete, readable implementation of all of this.

API reference (Flutter)

FileSyncConnect

MemberWhat it is
FileSyncConnect(appName: ...)Make one for your app. appName is what people see in DRL-FileSync.
start()Call once at startup. Restores an earlier connection and listens for DRL-FileSync's answer.
connect()Asks DRL-FileSync. Returns false when the DRL-FileSync app is not installed.
state, isConnectednotConnected, waiting or connected.
folderYour app's folder (see below). Only when connected.
folderNameThe folder's name on the PC, for showing to people.
message, messageIsErrorThe last thing worth telling the person, in plain words.
needsFileSyncApp, openInstallPage()Set when DRL-FileSync is missing; opens the download page.
reopen(), cancel()Open DRL-FileSync again for a request still waiting, or give up on it.
disconnect()Forget the connection here. The files stay on the PC.

It is a ChangeNotifier: use ListenableBuilder or addListener.

FileSyncFolder

MethodWhat it does
list(path)Names, sizes and whether each is a folder. 404 if the folder is not there.
read(path), write(path, bytes)Bytes in and out. write replaces what is there.
readString, writeString, readJson, writeJsonConveniences for text and JSON.
delete(path)Delete a file for good.
mkdir(path), mkdirs(path)Make a folder, or a whole path of folders.
exists(path)True when it is there.
spaceLeft()Bytes your app may still store.

Paths start with /. Every call can throw FileSyncException, which has status, code and a message written for people.

Errors, and what to show

statusMeaningWhat your app should do
404That file or folder is not there.Make the folder, or treat it as empty.
423The PC is locked (the person's key is not on it right now).Tell them to open DRL-FileSync on their phone, then try again.
503DRL-FileSync could not reach the PC (off, asleep, away).Show message. Keep the data on the phone and try again later.
507Your app's storage on the PC is full.Say so. The person can give it more space in DRL-FileSync.
403 / revokedThe person removed your app in DRL-FileSync.The library disconnects itself and sets message. Offer to connect again.
401The PC did not accept the request, usually because the phone's clock is wrong.Ask them to check the date and time.

The message of every error is written to be shown to a person as it is.

Testing without a phone or a PC

Open the live tester to connect a browser to your own PC and time create, read, update and delete calls.

The package has helpers for your own tests. Nothing to install and no DRL-FileSync needed.

a test
import 'package:filesync_connect/testing.dart';

test('saves a note', () async {
  final fileSync = FileSyncConnect.connectedTo(FakeFolder());   // already connected, files kept in memory
  await fileSync.folder.mkdirs('/notes');
  await fileSync.folder.writeString('/notes/a.txt', 'hi');
  expect(await fileSync.folder.readString('/notes/a.txt'), 'hi');
});
  • FakeFolder can fail on purpose (failNext = FileSyncException(503, 'Your PC is off.')) and can be made full.
  • FakePlatform plays the DRL-FileSync app, for testing your connect screen: installed or not, allow(...) and deny(...).

To try the real thing, install DRL-FileSync on your phone and PC and run your app. A simulator of DRL-FileSync, for developing without either, is coming.

Sample app: DRL-FileSync Notes

A notes app like Google Keep that backs up to the person's DRL-FileSync storage. It is the reference for a complete app: it keeps notes on the phone, connects with the button from step 3, backs up changed notes by itself, restores them on a new phone, and shows a clear message when the PC is off or the app was removed.

  • It backs up one small file per note, and keeps its own copy, so nothing is lost when the PC is off.
  • After connecting, what an earlier install saved comes back by itself.
  • All its DRL-FileSync code is in one file (backup_service.dart), on top of the package: what to send, when, and what to say to the person.

Download it with the SDK (in the samples folder)

Questions

Do I have to register, or pay?

No. There is no developer account, no approval and no fee. The package is free and open source (MIT). Your app's key is its identity.

What does the person see?

One screen in the DRL-FileSync app with your app's name, what it gets (one folder of its own, 5 GB), what it cannot do (see anything else), and Allow or Deny. They can remove your app later in DRL-FileSync, under Settings, DRL-FileSync Connect.

Does it work away from home?

Yes. The DRL-FileSync app carries your requests over the home Wi-Fi or over the internet, whichever it has. Your code does not change.

Does it work without the DRL-FileSync app?

No. The DRL-FileSync app asks the person and carries the requests, so it has to be on the phone. The library tells your app when it is not, and gives you a button that opens the download page.

What if my user reinstalls my app?

DRL-FileSync notices that an app with the same name has been there before and asks the person whether this is the same app installed again. If they say yes, your app gets its old folder back, and the earlier files are still there.

How big can a file be?

About 600 KB per request today. That is plenty for notes, settings and small documents. Larger files, in pieces, are planned.

What about iOS, the web and desktop apps?

Android comes first, with Flutter. A JavaScript version for web services is planned, which needs a way to reach the PC over the internet. We will say here when it is ready.

Is my users' data safe?

Your app only ever sees its own folder. Every request is signed with your app's key and checked by the PC, and the person can remove your app in one tap. We do not store or read your app's files: they live on the person's PC.