18.5. Design notes

A few design decisions are documented here. See also:

18.5.1. Client SQLCipher databases

The CamCOPS client uses two databases (typically called camcops_data.sqlite and camcops_sys.sqlite), stored in the device’s user-specific private area (e.g. ~/local/share/camcops under Linux). Note that some operating systems (e.g. Android, iOS) are designed for single-user use and don’t have the concept of a per-user private area. The ‘data’ database holds user data (patients, patient data) and the ‘sys’ database contains configuration information, stored strings, and the like. Both are encrypted with AES-256 via SQLCipher. They use the same passphrase for user convenience, but different encryption keys [1].

The decision to use two databases rather than one is so that, in emergencies, the ‘data’ database can be processed (viewed, rescued) without the need to share the ‘sys’ database and its information. It also simplifies the upload process a little (as the client can simply upload everything from the ‘data’ database and nothing from the ‘sys’ database).

18.5.2. Inline CSS

The server currently provides CSS inline. It could refer to CSS as files, so that browsers cache them better. However, inline CSS is still required for PDF creation, and it’s not clear this is an important performance constraint.

18.5.3. SFTP export

Not necessary, as one can mount an SFTP directory via NFS, then just export as a plain file.

18.5.4. Anonymisation

Proper anonymisation is Somebody Else’s Business; CamCOPS supports convenient export for subsequent anonymisation (see, for example, CRATE; https://github.com/RudolfCardinal/crate).

18.5.5. BLOB handling

  • It’s clearly preferable to have BLOBs in the database (rather than on the filesystem), both on the server and the client (server: easier to manage; client: within secure encrypted database).

  • It’s also clearly preferable to have BLOBs in their own table (server: doesn’t slow down all other tables; client: can upload record-by-record for BLOBs and table-by-table for other things).

  • Then, regarding storage/access within the client…

    • Writing images to a BLOB is a slow operation: it’s the QImage to QByteArray conversion (and reverse conversion) that’s relatively slow. This slows down rotation. The rotation operation itself, on a QImage, is fast.
  • Possible client strategies, then:

    • Blob/Field handle QByteArray only; QuImage deals with all the rotation. Slow for the client but simple.

    • Blob/Field deal with a combination of a QByteArray (written as PNG to the database) and a “rotation” field, and client/server rotate on the fly.

      • Should make the client rotation operation fast.

      • Could store rotation field in the task table, as before, but that is particularly inelegant.

      • Could make Blob and/or Field objects image-aware, and have them store rotation as an extra field in the blob table.

      • Also allows the potential to preserve more source information, e.g. EXIF, because no image manipulations are stored.

      • … for example:

        new integer field: blob.image_rotation_deg_ccw
        new text field: blob.filetype   // e.g. "png"
        QImage Blob::image() const;  // and cache it
        void Blob::rotateImage(int angle);
        void Blob::setImage(const QImage& image);
        QImage FieldRef::image() const;
        void FieldRef::rotateImage(int angle);
    • Store only final rotated images and do the rotation in the background.

  • I tried the Blob method (with rotation as a field in the Blob table) – massively faster than before. Makes the difference between dire and respectable performance.