Skip to content

Commit f29b49f

Browse files
committed
Added a way to load/save payload from file (available only on Desktop OSes)
1 parent e7d6a3e commit f29b49f

File tree

6 files changed

+267
-22
lines changed

6 files changed

+267
-22
lines changed

src/cloudsync.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,108 @@ void cloudsync_payload_decode (sqlite3_context *context, int argc, sqlite3_value
22582258
cloudsync_payload_apply(context, payload, blen);
22592259
}
22602260

2261+
// MARK: - Payload load/store -
2262+
2263+
#ifdef CLOUDSYNC_DESKTOP_OS
2264+
2265+
int cloudsync_payload_get (sqlite3_context *context, char **blob, int *blob_size, int *db_version, int *seq, sqlite3_int64 *new_db_version, sqlite3_int64 *new_seq) {
2266+
sqlite3 *db = sqlite3_context_db_handle(context);
2267+
2268+
*db_version = dbutils_settings_get_int_value(db, CLOUDSYNC_KEY_SEND_DBVERSION);
2269+
if (*db_version < 0) {sqlite3_result_error(context, "Unable to retrieve db_version.", -1); return SQLITE_ERROR;}
2270+
2271+
*seq = dbutils_settings_get_int_value(db, CLOUDSYNC_KEY_SEND_SEQ);
2272+
if (*seq < 0) {sqlite3_result_error(context, "Unable to retrieve seq.", -1); return SQLITE_ERROR;}
2273+
2274+
// retrieve BLOB
2275+
char sql[1024];
2276+
snprintf(sql, sizeof(sql), "WITH max_db_version AS (SELECT MAX(db_version) AS max_db_version FROM cloudsync_changes) "
2277+
"SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq), max_db_version AS max_db_version, MAX(IIF(db_version = max_db_version, seq, NULL)) FROM cloudsync_changes, max_db_version WHERE site_id=cloudsync_siteid() AND (db_version>%d OR (db_version=%d AND seq>%d))", *db_version, *db_version, *seq);
2278+
2279+
int rc = dbutils_blob_int_int_select(db, sql, blob, blob_size, new_db_version, new_seq);
2280+
if (rc != SQLITE_OK) {
2281+
sqlite3_result_error(context, "cloudsync_network_send_changes unable to get changes", -1);
2282+
sqlite3_result_error_code(context, rc);
2283+
return rc;
2284+
}
2285+
2286+
// exit if there is no data to send
2287+
if (blob == NULL || blob_size == 0) return SQLITE_OK;
2288+
return rc;
2289+
}
2290+
2291+
void cloudsync_payload_save (sqlite3_context *context, int argc, sqlite3_value **argv) {
2292+
DEBUG_FUNCTION("cloudsync_payload_save");
2293+
2294+
// sanity check argument
2295+
if (sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
2296+
sqlite3_result_error(context, "Unable to retrieve file path.", -1);
2297+
return;
2298+
}
2299+
2300+
// retrieve full path to file
2301+
const char *path = (const char *)sqlite3_value_text(argv[0]);
2302+
file_delete(path);
2303+
2304+
// retrieve payload
2305+
char *blob = NULL;
2306+
int blob_size = 0, db_version = 0, seq = 0;
2307+
sqlite3_int64 new_db_version = 0, new_seq = 0;
2308+
int rc = cloudsync_payload_get(context, &blob, &blob_size, &db_version, &seq, &new_db_version, &new_seq);
2309+
if (rc != SQLITE_OK) return;
2310+
2311+
// exit if there is no data to send
2312+
if (blob == NULL || blob_size == 0) return;
2313+
2314+
// write payload to file
2315+
bool res = file_write(path, blob, (size_t)blob_size);
2316+
sqlite3_free(blob);
2317+
2318+
if (res == false) {
2319+
sqlite3_result_error(context, "Unable to write payload to file path.", -1);
2320+
return;
2321+
}
2322+
2323+
// update db_version and seq
2324+
char buf[256];
2325+
sqlite3 *db = sqlite3_context_db_handle(context);
2326+
if (new_db_version != db_version) {
2327+
snprintf(buf, sizeof(buf), "%lld", new_db_version);
2328+
dbutils_settings_set_key_value(db, context, CLOUDSYNC_KEY_SEND_DBVERSION, buf);
2329+
}
2330+
if (new_seq != seq) {
2331+
snprintf(buf, sizeof(buf), "%lld", new_seq);
2332+
dbutils_settings_set_key_value(db, context, CLOUDSYNC_KEY_SEND_SEQ, buf);
2333+
}
2334+
}
2335+
2336+
void cloudsync_payload_load (sqlite3_context *context, int argc, sqlite3_value **argv) {
2337+
DEBUG_FUNCTION("cloudsync_payload_load");
2338+
2339+
// sanity check argument
2340+
if (sqlite3_value_type(argv[0]) != SQLITE_TEXT) {
2341+
sqlite3_result_error(context, "Unable to retrieve file path.", -1);
2342+
return;
2343+
}
2344+
2345+
// retrieve full path to file
2346+
const char *path = (const char *)sqlite3_value_text(argv[0]);
2347+
file_delete(path);
2348+
2349+
size_t payload_size = 0;
2350+
char *payload = file_read(path, &payload_size);
2351+
if (!payload) {
2352+
if (payload_size == -1) sqlite3_result_error(context, "Unable to read payload from file path.", -1);
2353+
return;
2354+
}
2355+
2356+
int nrows = cloudsync_payload_apply (context, payload, (int)payload_size);
2357+
if (payload) cloudsync_memory_free(payload);
2358+
if (nrows != -1) sqlite3_result_int(context, nrows);
2359+
}
2360+
2361+
#endif
2362+
22612363
// MARK: - Public -
22622364

22632365
void cloudsync_version (sqlite3_context *context, int argc, sqlite3_value **argv) {
@@ -3331,6 +3433,14 @@ int cloudsync_register (sqlite3 *db, char **pzErrMsg) {
33313433
rc = dbutils_register_function(db, "cloudsync_payload_decode", cloudsync_payload_decode, -1, pzErrMsg, ctx, NULL);
33323434
if (rc != SQLITE_OK) return rc;
33333435

3436+
#ifdef CLOUDSYNC_DESKTOP_OS
3437+
rc = dbutils_register_function(db, "cloudsync_payload_save", cloudsync_payload_save, 1, pzErrMsg, ctx, NULL);
3438+
if (rc != SQLITE_OK) return rc;
3439+
3440+
rc = dbutils_register_function(db, "cloudsync_payload_load", cloudsync_payload_load, 1, pzErrMsg, ctx, NULL);
3441+
if (rc != SQLITE_OK) return rc;
3442+
#endif
3443+
33343444
// PRIVATE functions
33353445
rc = dbutils_register_function(db, "cloudsync_is_sync", cloudsync_is_sync, 1, pzErrMsg, ctx, NULL);
33363446
if (rc != SQLITE_OK) return rc;

src/cloudsync.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
extern "C" {
2121
#endif
2222

23-
#define CLOUDSYNC_VERSION "0.8.32"
23+
#define CLOUDSYNC_VERSION "0.8.33"
2424

2525
int sqlite3_cloudsync_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);
2626

src/cloudsync_private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const char *cloudsync_context_init (sqlite3 *db, cloudsync_context *data, sqlite
3737
void *cloudsync_get_auxdata (sqlite3_context *context);
3838
void cloudsync_set_auxdata (sqlite3_context *context, void *xdata);
3939
int cloudsync_payload_apply (sqlite3_context *context, const char *payload, int blen);
40+
int cloudsync_payload_get (sqlite3_context *context, char **blob, int *blob_size, int *db_version, int *seq, sqlite3_int64 *new_db_version, sqlite3_int64 *new_seq);
4041

4142
// used by core
4243
typedef bool (*cloudsync_payload_apply_callback_t)(void **xdata, cloudsync_pk_decode_bind_context *decoded_change, sqlite3 *db, cloudsync_context *data, int step, int rc);

src/network.c

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -693,28 +693,12 @@ int cloudsync_network_send_changes_internal (sqlite3_context *context, int argc,
693693
network_data *data = (network_data *)cloudsync_get_auxdata(context);
694694
if (!data) {sqlite3_result_error(context, "Unable to retrieve CloudSync context.", -1); return SQLITE_ERROR;}
695695

696-
sqlite3 *db = sqlite3_context_db_handle(context);
697-
698-
int db_version = dbutils_settings_get_int_value(db, CLOUDSYNC_KEY_SEND_DBVERSION);
699-
if (db_version<0) {sqlite3_result_error(context, "Unable to retrieve db_version.", -1); return SQLITE_ERROR;}
700-
701-
int seq = dbutils_settings_get_int_value(db, CLOUDSYNC_KEY_SEND_SEQ);
702-
if (seq<0) {sqlite3_result_error(context, "Unable to retrieve seq.", -1); return SQLITE_ERROR;}
703-
704-
// retrieve BLOB
705-
char sql[1024];
706-
snprintf(sql, sizeof(sql), "WITH max_db_version AS (SELECT MAX(db_version) AS max_db_version FROM cloudsync_changes) "
707-
"SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq), max_db_version AS max_db_version, MAX(IIF(db_version = max_db_version, seq, NULL)) FROM cloudsync_changes, max_db_version WHERE site_id=cloudsync_siteid() AND (db_version>%d OR (db_version=%d AND seq>%d))", db_version, db_version, seq);
708-
int blob_size = 0;
696+
// retrieve payload
709697
char *blob = NULL;
710-
sqlite3_int64 new_db_version = 0;
711-
sqlite3_int64 new_seq = 0;
712-
int rc = dbutils_blob_int_int_select(db, sql, &blob, &blob_size, &new_db_version, &new_seq);
713-
if (rc != SQLITE_OK) {
714-
sqlite3_result_error(context, "cloudsync_network_send_changes unable to get changes", -1);
715-
sqlite3_result_error_code(context, rc);
716-
return rc;
717-
}
698+
int blob_size = 0, db_version = 0, seq = 0;
699+
sqlite3_int64 new_db_version = 0, new_seq = 0;
700+
int rc = cloudsync_payload_get(context, &blob, &blob_size, &db_version, &seq, &new_db_version, &new_seq);
701+
if (rc != SQLITE_OK) return rc;
718702

719703
// exit if there is no data to send
720704
if (blob == NULL || blob_size == 0) return SQLITE_OK;
@@ -749,7 +733,9 @@ int cloudsync_network_send_changes_internal (sqlite3_context *context, int argc,
749733
return SQLITE_ERROR;
750734
}
751735

736+
// update db_version and seq
752737
char buf[256];
738+
sqlite3 *db = sqlite3_context_db_handle(context);
753739
if (new_db_version != db_version) {
754740
snprintf(buf, sizeof(buf), "%lld", new_db_version);
755741
dbutils_settings_set_key_value(db, context, CLOUDSYNC_KEY_SEND_DBVERSION, buf);

src/utils.c

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,23 @@
1414
#include <objbase.h>
1515
#include <bcrypt.h>
1616
#include <ntstatus.h> //for STATUS_SUCCESS
17+
#include <io.h>
18+
#define file_close _close
1719
#else
1820
#include <unistd.h>
1921
#if defined(__APPLE__)
2022
#include <Security/Security.h>
2123
#elif !defined(__ANDROID__)
2224
#include <sys/random.h>
2325
#endif
26+
#define file_close close
27+
#endif
28+
29+
#ifdef CLOUDSYNC_DESKTOP_OS
30+
#include <fcntl.h>
31+
#include <errno.h>
32+
#include <sys/stat.h>
33+
#include <sys/types.h>
2434
#endif
2535

2636
#ifndef SQLITE_CORE
@@ -280,6 +290,124 @@ uint64_t fnv1a_hash (const char *data, size_t len) {
280290

281291
return h_final;
282292
}
293+
294+
// MARK: - Files -
295+
296+
#ifdef CLOUDSYNC_DESKTOP_OS
297+
298+
bool file_delete (const char *path) {
299+
#ifdef _WIN32
300+
return DeleteFileA(path);
301+
#else
302+
return (unlink(path) == 0);
303+
#endif
304+
}
305+
306+
static bool file_read_all (int fd, char *buf, size_t n) {
307+
size_t off = 0;
308+
while (off < n) {
309+
#ifdef _WIN32
310+
int r = _read(fd, buf + off, (unsigned)(n - off));
311+
if (r <= 0) return false;
312+
#else
313+
ssize_t r = read(fd, buf + off, n - off);
314+
if (r < 0) {
315+
if (errno == EINTR) continue;
316+
return false;
317+
}
318+
if (r == 0) return false; // unexpected EOF
319+
#endif
320+
off += (size_t)r;
321+
}
322+
return true;
323+
}
324+
325+
char *file_read (const char *path, size_t *len) {
326+
int fd = -1;
327+
char *buffer = NULL;
328+
329+
#ifdef _WIN32
330+
fd = _open(path, _O_RDONLY | _O_BINARY);
331+
#else
332+
fd = open(path, O_RDONLY);
333+
#endif
334+
if (fd < 0) goto abort_read;
335+
336+
// Get size after open to reduce TOCTTOU
337+
#ifdef _WIN32
338+
struct _stat64 st;
339+
if (_fstat64(fd, &st) != 0 || st.st_size < 0) goto abort_read;
340+
int64_t isz = st.st_size;
341+
#else
342+
struct stat st;
343+
if (fstat(fd, &st) != 0 || st.st_size < 0) goto abort_read;
344+
int64_t isz = st.st_size;
345+
#endif
346+
347+
size_t sz = (size_t)isz;
348+
// optional: guard against huge files that don't fit in size_t
349+
if ((int64_t)sz != isz) goto abort_read;
350+
351+
buffer = (char *)cloudsync_memory_alloc(sz + 1);
352+
if (!buffer) goto abort_read;
353+
buffer[sz] = '\0';
354+
355+
if (!file_read_all(fd, buffer, sz)) goto abort_read;
356+
if (len) *len = sz;
357+
358+
file_close(fd);
359+
return buffer;
360+
361+
abort_read:
362+
if (len) *len = -1;
363+
if (buffer) cloudsync_memory_free(buffer);
364+
if (fd >= 0) file_close(fd);
365+
return NULL;
366+
}
367+
368+
int file_create (const char *path) {
369+
#ifdef _WIN32
370+
int flags = _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY;
371+
int mode = _S_IWRITE; // Windows ignores most POSIX perms
372+
return _open(path, flags, mode);
373+
#else
374+
int flags = O_WRONLY | O_CREAT | O_TRUNC;
375+
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
376+
return open(path, flags, mode);
377+
#endif
378+
}
379+
380+
static bool file_write_all (int fd, const char *buf, size_t n) {
381+
size_t off = 0;
382+
while (off < n) {
383+
#ifdef _WIN32
384+
int w = _write(fd, buf + off, (unsigned)(n - off));
385+
if (w <= 0) return false;
386+
#else
387+
ssize_t w = write(fd, buf + off, n - off);
388+
if (w < 0) {
389+
if (errno == EINTR) continue;
390+
return false;
391+
}
392+
if (w == 0) return false;
393+
#endif
394+
off += (size_t)w;
395+
}
396+
return true;
397+
}
398+
399+
bool file_write (const char *path, const char *buffer, size_t len) {
400+
int fd = file_create(path);
401+
if (fd < 0) return false;
402+
403+
bool res = file_write_all(fd, buffer, len);
404+
405+
file_close(fd);
406+
return res;
407+
}
408+
409+
#endif
410+
283411
// MARK: - CRDT algos -
284412

285413
table_algo crdt_algo_from_name (const char *algo_name) {

src/utils.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@
1515
#include <strings.h>
1616
#include <string.h>
1717

18+
// CLOUDSYNC_DESKTOP_OS = 1 if compiling for macOS, Linux (desktop), or Windows
19+
// Not set for iOS, Android, WebAssembly, or other platforms
20+
#if defined(_WIN32) && !defined(__ANDROID__) && !defined(__EMSCRIPTEN__)
21+
#define CLOUDSYNC_DESKTOP_OS 1
22+
#elif defined(__APPLE__) && defined(__MACH__)
23+
#include <TargetConditionals.h>
24+
#if TARGET_OS_OSX
25+
#define CLOUDSYNC_DESKTOP_OS 1
26+
#endif
27+
#elif defined(__linux__) && !defined(__ANDROID__) && !defined(__EMSCRIPTEN__)
28+
#define CLOUDSYNC_DESKTOP_OS 1
29+
#endif
30+
1831
#ifndef SQLITE_CORE
1932
#include "sqlite3ext.h"
2033
#else
@@ -135,4 +148,11 @@ int cloudsync_blob_compare(const char *blob1, size_t size1, const char *blob2, s
135148

136149
void cloudsync_rowid_decode (sqlite3_int64 rowid, sqlite3_int64 *db_version, sqlite3_int64 *seq);
137150

151+
// available only on Desktop OS
152+
#ifdef CLOUDSYNC_DESKTOP_OS
153+
bool file_delete (const char *path);
154+
char *file_read (const char *path, size_t *len);
155+
bool file_write (const char *path, const char *buffer, size_t len);
156+
#endif
157+
138158
#endif

0 commit comments

Comments
 (0)