module Agents class DropboxWatchAgent < Agent include DropboxConcern cannot_receive_events! default_schedule "every_1m" description <<~MD The Dropbox Watch Agent watches the given `dir_to_watch` and emits events with the detected changes. #{'## Include the `dropbox-api` and `omniauth-dropbox` gems in your `Gemfile` and set `DROPBOX_OAUTH_KEY` and `DROPBOX_OAUTH_SECRET` in your environment to use Dropbox Agents.' if dependencies_missing?} MD event_description <<~MD The event payload will contain the following fields: { "added": [ { "path": "/path/to/added/file", "rev": "1526952fd5", "modified": "2017-10-14T18:39:41Z" } ], "removed": [ ... ], "updated": [ ... ] } MD def default_options { 'dir_to_watch' => '/', 'expected_update_period_in_days' => 1 } end def validate_options errors.add(:base, 'The `dir_to_watch` property is required.') unless options['dir_to_watch'].present? errors.add(:base, 'Invalid `expected_update_period_in_days` format.') unless options['expected_update_period_in_days'].present? && is_positive_integer?(options['expected_update_period_in_days']) end def working? event_created_within?(interpolated['expected_update_period_in_days']) && !received_event_without_error? end def check current_contents = ls(interpolated['dir_to_watch']) diff = DropboxDirDiff.new(previous_contents, current_contents) create_event(payload: diff.to_hash) unless previous_contents.nil? || diff.empty? remember(current_contents) end private def ls(dir_to_watch) dropbox.ls(dir_to_watch) .select { |entry| entry.respond_to?(:rev) } .map { |file| { 'path' => file.path, 'rev' => file.rev, 'modified' => file.server_modified } } end def previous_contents self.memory['contents'] end def remember(contents) self.memory['contents'] = contents end # == Auxiliary classes == class DropboxDirDiff def initialize(previous, current) @previous = previous || [] @current = current || [] end def empty? (@previous == @current) end def to_hash calculate_diff { added: @added, removed: @removed, updated: @updated } end private def calculate_diff @updated = @current.select do |current_entry| previous_entry = find_by_path(@previous, current_entry['path']) (current_entry != previous_entry) && !previous_entry.nil? end updated_entries = @updated + @previous.select do |previous_entry| find_by_path(@updated, previous_entry['path']) end @added = @current - @previous - updated_entries @removed = @previous - @current - updated_entries end def find_by_path(array, path) array.find { |entry| entry['path'] == path } end end end end