zdir_patch_t * zdir_patch_new (const char *path, zfile_t *file, zdir_patch_op_t op, const char *alias) { zdir_patch_t *self = (zdir_patch_t *) zmalloc (sizeof (zdir_patch_t)); if (!self) return NULL; self->path = strdup (path); if (self->path) self->file = zfile_dup (file); if (!self->file) { zdir_patch_destroy (&self); return NULL; } self->op = op; // Calculate virtual path for patch (remove path, prefix alias) char *filename = zfile_filename (file, path); if (!filename) { zdir_patch_destroy (&self); return NULL; } assert (*filename != '/'); self->vpath = (char *) zmalloc (strlen (alias) + strlen (filename) + 2); if (alias [strlen (alias) - 1] == '/') sprintf (self->vpath, "%s%s", alias, filename); else sprintf (self->vpath, "%s/%s", alias, filename); return self; }
static void sub_patch_add (sub_t *self, zdir_patch_t *patch) { // Skip file creation if client already has identical file zdir_patch_digest_set (patch); if (zdir_patch_op (patch) == patch_create) { char *digest = (char *) zhash_lookup (self->cache, zdir_patch_vpath (patch) + strlen(self->path) + 1); if (digest && streq (digest, zdir_patch_digest (patch))) return; // Just skip patch for this client } // Remove any previous patches for the same file zdir_patch_t *existing = (zdir_patch_t *) zlist_first (self->client->patches); while (existing) { if (streq (zdir_patch_vpath (patch), zdir_patch_vpath (existing))) { zlist_remove (self->client->patches, existing); zdir_patch_destroy (&existing); break; } existing = (zdir_patch_t *) zlist_next (self->client->patches); } if (zdir_patch_op (patch) == patch_create) zhash_insert (self->cache, zdir_patch_digest (patch), zdir_patch_vpath (patch)); // Track that we've queued patch for client, so we don't do it twice zlist_append (self->client->patches, zdir_patch_dup (patch)); }
static bool mount_refresh (mount_t *self, server_t *server) { bool activity = false; // Get latest snapshot and build a patches list for any changes zdir_t *latest = zdir_new (self->location, NULL); zlist_t *patches = zdir_diff (self->dir, latest, self->alias); // Drop old directory and replace with latest version zdir_destroy (&self->dir); self->dir = latest; // Copy new patches to clients' patches list sub_t *sub = (sub_t *) zlist_first (self->subs); while (sub) { zdir_patch_t *patch = (zdir_patch_t *) zlist_first (patches); while (patch) { sub_patch_add (sub, patch); patch = (zdir_patch_t *) zlist_next (patches); activity = true; } sub = (sub_t *) zlist_next (self->subs); } // Destroy patches, they've all been copied while (zlist_size (patches)) { zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches); zdir_patch_destroy (&patch); } zlist_destroy (&patches); return activity; }
static void s_check_directory (s_agent_t *self) { // Get latest snapshot and build a patches list for any changes // All patches are built using a virtual path starting at "/" zdir_t *dir = zdir_new (self->path, NULL); zlist_t *patches = zdir_diff (self->dir, dir, "/"); // Drop old directory and replace with latest version zdir_destroy (&self->dir); self->dir = dir; while (zlist_size (patches)) { zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches); if (zdir_patch_op (patch) == patch_create) { // Shout new files to DROPS group // Stupidest possible approach: send whole file as one frame // Truncate file at arbitrary limit of 10MB zfile_t *file = zdir_patch_file (patch); if (zfile_input (file) == 0) { zchunk_t *chunk = zfile_read (file, 10 * 1024 * 1024, 0); assert (chunk); zmsg_t *msg = zmsg_new (); zmsg_addstr (msg, "CREATE"); zmsg_addstr (msg, zdir_patch_vpath (patch)); zmsg_add (msg, zframe_new (zchunk_data (chunk), zchunk_size (chunk))); zchunk_destroy (&chunk); zyre_shout (self->zyre, "DROPS", &msg); } } zdir_patch_destroy (&patch); } zlist_destroy (&patches); }
void zdir_patch_test (bool verbose) { printf (" * zdir_patch: "); // @selftest zfile_t *file = zfile_new (".", "bilbo"); assert (file); zdir_patch_t *patch = zdir_patch_new (".", file, patch_create, "/"); assert (patch); zfile_destroy (&file); file = zdir_patch_file (patch); assert (file); assert (streq (zfile_filename (file, "."), "bilbo")); assert (streq (zdir_patch_vpath (patch), "/bilbo")); zdir_patch_destroy (&patch); #if defined (__WINDOWS__) zsys_shutdown(); #endif // @end printf ("OK\n"); }
static void client_destroy (client_t **self_p) { assert (self_p); if (*self_p) { client_t *self = *self_p; zframe_destroy (&self->address); fmq_msg_destroy (&self->request); fmq_msg_destroy (&self->reply); free (self->hashkey); while (zlist_size (self->patches)) { zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (self->patches); zdir_patch_destroy (&patch); } zlist_destroy (&self->patches); free (self); *self_p = NULL; } }
static void mount_sub_store (mount_t *self, client_t *client, fmq_msg_t *request) { assert (self); assert (self->subs); // Store subscription along with any previous ones // Coalesce subscriptions that are on same path char *path = fmq_msg_path (request); sub_t *sub = (sub_t *) zlist_first (self->subs); while (sub) { if (client == sub->client) { // If old subscription is superset/same as new, ignore new if (strncmp (path, sub->path, strlen (sub->path)) == 0) return; else // If new subscription is superset of old one, remove old if (strncmp (sub->path, path, strlen (path)) == 0) { zlist_remove (self->subs, sub); sub_destroy (&sub); sub = (sub_t *) zlist_first (self->subs); } else sub = (sub_t *) zlist_next (self->subs); } else sub = (sub_t *) zlist_next (self->subs); } // New subscription for this client, append to our list sub = sub_new (client, path, fmq_msg_cache (request)); zlist_append (self->subs, sub); // If client requested resync, send full mount contents now if (fmq_msg_options_number (client->request, "RESYNC", 0) == 1) { zlist_t *patches = zdir_resync (self->dir, self->alias); while (zlist_size (patches)) { zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches); sub_patch_add (sub, patch); zdir_patch_destroy (&patch); } zlist_destroy (&patches); } }
// -------------------------------------------------------------------------- // Self test of this class int zdir_patch_test (bool verbose) { printf (" * zdir_patch: "); // @selftest zfile_t *file = zfile_new (".", "bilbo"); zdir_patch_t *patch = zdir_patch_new (".", file, patch_create, "/"); zfile_destroy (&file); file = zdir_patch_file (patch); assert (streq (zfile_filename (file, "."), "bilbo")); assert (streq (zdir_patch_vpath (patch), "/bilbo")); zdir_patch_destroy (&patch); // @end printf ("OK\n"); return 0; }
zdir_patch_t * zdir_patch_dup (zdir_patch_t *self) { if (self) { zdir_patch_t *copy = (zdir_patch_t *) zmalloc (sizeof (zdir_patch_t)); if (copy) { copy->op = self->op; copy->path = strdup (self->path); if (copy->path) copy->file = zfile_dup (self->file); if (copy->file) copy->vpath = strdup (self->vpath); if (copy->vpath) // Don't recalculate hash when we duplicate patch copy->digest = self->digest ? strdup (self->digest) : NULL; if (copy->digest == NULL) zdir_patch_destroy (©); } return copy; } else return NULL; }
static void get_next_patch_for_client (server_t *self, client_t *client) { // Get next patch for client if we're not doing one already if (client->patch == NULL) client->patch = (zdir_patch_t *) zlist_pop (client->patches); if (client->patch == NULL) { client->next_event = finished_event; return; } // Get virtual path from patch fmq_msg_set_filename (client->reply, zdir_patch_vpath (client->patch)); // We can process a delete patch right away if (zdir_patch_op (client->patch) == patch_delete) { fmq_msg_set_sequence (client->reply, client->sequence++); fmq_msg_set_operation (client->reply, FMQ_MSG_FILE_DELETE); client->next_event = send_delete_event; // No reliability in this version, assume patch delivered safely zdir_patch_destroy (&client->patch); } else if (zdir_patch_op (client->patch) == patch_create) { // Create patch refers to file, open that for input if needed if (client->file == NULL) { client->file = zfile_dup (zdir_patch_file (client->patch)); if (zfile_input (client->file)) { // File no longer available, skip it zdir_patch_destroy (&client->patch); zfile_destroy (&client->file); client->next_event = next_patch_event; return; } client->offset = 0; } // Get next chunk for file zchunk_t *chunk = zfile_read (client->file, CHUNK_SIZE, client->offset); assert (chunk); // Check if we have the credit to send chunk if (zchunk_size (chunk) <= client->credit) { fmq_msg_set_sequence (client->reply, client->sequence++); fmq_msg_set_operation (client->reply, FMQ_MSG_FILE_CREATE); fmq_msg_set_offset (client->reply, client->offset); fmq_msg_set_chunk (client->reply, zframe_new ( zchunk_data (chunk), zchunk_size (chunk))); client->offset += zchunk_size (chunk); client->credit -= zchunk_size (chunk); client->next_event = send_chunk_event; // Zero-sized chunk means end of file if (zchunk_size (chunk) == 0) { zfile_destroy (&client->file); zdir_patch_destroy (&client->patch); } } else client->next_event = no_credit_event; zchunk_destroy (&chunk); } }
/// // Destroy a patch QZdirPatch::~QZdirPatch () { zdir_patch_destroy (&self); }
void zdir_test (bool verbose) { printf (" * zdir: "); // @selftest // need to create a file in the test directory we're watching // in order to ensure the directory exists zfile_t *initfile = zfile_new ("./zdir-test-dir", "initial_file"); assert (initfile); zfile_output (initfile); fprintf (zfile_handle (initfile), "initial file\n"); zfile_close (initfile); zfile_destroy (&initfile); zdir_t *older = zdir_new ("zdir-test-dir", NULL); assert (older); if (verbose) { printf ("\n"); zdir_dump (older, 0); } zdir_t *newer = zdir_new (".", NULL); assert (newer); zlist_t *patches = zdir_diff (older, newer, "/"); assert (patches); while (zlist_size (patches)) { zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches); zdir_patch_destroy (&patch); } zlist_destroy (&patches); zdir_destroy (&older); zdir_destroy (&newer); zdir_t *nosuch = zdir_new ("does-not-exist", NULL); assert (nosuch == NULL); // zdir_watch test: zactor_t *watch = zactor_new (zdir_watch, NULL); assert (watch); int synced; if (verbose) { zsock_send (watch, "s", "VERBOSE"); synced = zsock_wait(watch); assert ( synced == 0); } zclock_sleep (1001); // wait for initial file to become 'stable' zsock_send (watch, "si", "TIMEOUT", 100); synced = zsock_wait(watch); assert (synced == 0); zsock_send (watch, "ss", "SUBSCRIBE", "zdir-test-dir"); synced = zsock_wait(watch); assert(synced == 0); zsock_send (watch, "ss", "UNSUBSCRIBE", "zdir-test-dir"); synced = zsock_wait(watch); assert(synced == 0); zsock_send (watch, "ss", "SUBSCRIBE", "zdir-test-dir"); synced = zsock_wait(watch); assert(synced == 0); zfile_t *newfile = zfile_new ("zdir-test-dir", "test_abc"); zfile_output (newfile); fprintf (zfile_handle (newfile), "test file\n"); zfile_close (newfile); zpoller_t *watch_poll = zpoller_new (watch, NULL); // poll for a certain timeout before giving up and failing the test. void* polled = zpoller_wait(watch_poll, 1001); assert (polled == watch); // wait for notification of the file being added char *path; int rc = zsock_recv (watch, "sp", &path, &patches); assert (rc == 0); assert (streq (path, "zdir-test-dir")); freen (path); assert (zlist_size (patches) == 1); zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches); assert (streq (zdir_patch_path (patch), "zdir-test-dir")); zfile_t *patch_file = zdir_patch_file (patch); assert (streq (zfile_filename (patch_file, ""), "zdir-test-dir/test_abc")); zdir_patch_destroy (&patch); zlist_destroy (&patches); // remove the file zfile_remove (newfile); zfile_destroy (&newfile); // poll for a certain timeout before giving up and failing the test. polled = zpoller_wait(watch_poll, 1001); assert (polled == watch); // wait for notification of the file being removed rc = zsock_recv (watch, "sp", &path, &patches); assert (rc == 0); assert (streq (path, "zdir-test-dir")); freen (path); assert (zlist_size (patches) == 1); patch = (zdir_patch_t *) zlist_pop (patches); assert (streq (zdir_patch_path (patch), "zdir-test-dir")); patch_file = zdir_patch_file (patch); assert (streq (zfile_filename (patch_file, ""), "zdir-test-dir/test_abc")); zdir_patch_destroy (&patch); zlist_destroy (&patches); zpoller_destroy (&watch_poll); zactor_destroy (&watch); // clean up by removing the test directory. zdir_t *testdir = zdir_new ("zdir-test-dir", NULL); zdir_remove (testdir, true); zdir_destroy (&testdir); #if defined (__WINDOWS__) zsys_shutdown(); #endif // @end printf ("OK\n"); }