retgoat/syncthing-ruby

View on GitHub
lib/syncthing.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require "syncthing/version"
require "json"
require "net/http"

class SyncthingApiError < StandardError; end

class SyncthingClient
  ENDPOINTS = {
    # System endpoints
    :version => { :name=>'/system/version', :method => :get },
    :connections => { :name=>'/system/connections', :method => :get },
    :config => {
      :get => { :name=>'/system/config', :method => :get },
      :new => { :name => '/system/config', :method => :post },
      :insync => { :name => '/system/config/insync', :method => :get }
    },
    :system => {
      :restart => { :name => '/system/restart', :method => :post },
      :reset => { :name => '/system/reset', :method => :post },
      :shutdown => { :name => '/system/shutdown', :method => :post },
      :ping => { :name => '/system/ping', :method => :get },
      :upgrade => { :name => '/system/upgrade', :method => :get },
      :do_upgrade => { :name => '/system/upgrade', :method => :post },
      :status => { :name => '/system/status', :method => :get },
    },
    :errors => {
      :get => { :name => '/system/error', :method => :get },
      :new => { :name => '/system/error', :method => :post },
      :clear => { :name => '/system/error/clear', :method => :post }
    },
    :discovery => {
     :get => { :name => '/system/discovery', :method => :get },
     :new => { :name => '/system/discovery/hint', :method => :post }
    },
    # Database endpoints
    :db => {
      :browse => { :name => '/db/browse', :method => :get },
      :completion => { :name => '/db/completion', :method => :get },
      :file => { :name => '/db/file', :method => :get },
      :ignores => { :name => '/db/ignores', :method => :get },
      :new_ignores => { :name => '/db/ignores', :method => :post },
      :need => { :name => '/db/need', :method => :get },
      :prio => { :name => '/db/prio', :method => :post },
      :scan => { :name => '/db/scan', :method => :post },
      :status => { :name => '/db/status', :method => :get },
    },
    # Statistics endpoints
    :stats => {
      :device => { :name => '/stats/device', :method => :get },
      :folder => { :name => '/stats/folder', :method => :get }
    }
  }

  def initialize (apikey, url = 'https://localhost:8080')
    @syncthing_url = "#{url}/rest"
    @syncthing_apikey = apikey
  end # initialize

  # System
  def get_version
    api_call(ENDPOINTS[:version])
  end

  def get_connections
    api_call(ENDPOINTS[:connections])
  end

  def get_config
    api_call(ENDPOINTS[:config][:get])
  end

  def get_insync
    api_call(ENDPOINTS[:config][:insync])
  end

  def get_errors
    api_call(ENDPOINTS[:errors][:get])
  end

  def get_discovery
    api_call(ENDPOINTS[:discovery][:get])
  end

  def new_error error_body
    api_call(ENDPOINTS[:errors][:new], { error: error_body.to_str }, false)
  end

  def clear_errors
    api_call(ENDPOINTS[:errors][:clear])
  end

  # def new_discovery_hint node, addr
  #   api_call(ENDPOINTS[:discovery][:new], false, get_params_string({ device: node, addr: addr }))
  # end

  def new_config config
    api_call(ENDPOINTS[:config][:new], config, false)
  end

  def restart!
    api_call(ENDPOINTS[:system][:restart])
  end

  def reset!
    api_call(ENDPOINTS[:system][:reset])
  end

  def shutdown!
    api_call(ENDPOINTS[:system][:shutdown])
  end

  def upgrade
    api_call(ENDPOINTS[:system][:upgrade])
  end

  def upgrade!
    api_call(ENDPOINTS[:system][:do_upgrade])
  end

  def get_status
    api_call(ENDPOINTS[:system][:status])
  end

  def get_ping
    api_call(ENDPOINTS[:system][:ping])
  end

  def new_ping
    get_ping
  end

  # Database
  def browse_databse folder, level = false, prefix = false
    api_call(ENDPOINTS[:db][:browse], false, get_params_string({ folder: folder, level: level, prefix: prefix} ))
  end

  def get_completion device_id, folder
    api_call(ENDPOINTS[:db][:completion], false, get_params_string({ device: device_id, folder: folder }))
  end

  def get_file file
    api_call(ENDPOINTS[:db][:file], false, get_params_string({ file: file }))
  end

  def get_ignores folder
    api_call(ENDPOINTS[:db][:ignores], false, get_params_string({ folder: folder }))
  end

  def new_ignores folder, ignores
    api_call(ENDPOINTS[:db][:new_ignores], ignores, get_params_string({ folder: folder }))
  end

  def get_need folder
    api_call(ENDPOINTS[:db][:need], false, get_params_string({ folder: folder }))
  end

  def assign_priority folder, file
    api_call(ENDPOINTS[:db][:prio], false, get_params_string({ folder: folder, file: file }))
  end

  def scan folder, subfolder = false
    api_call(ENDPOINTS[:db][:scan], false, get_params_string({ folder: folder, sub: subfolder }))
  end

  def get_folder_status folder
    api_call(ENDPOINTS[:db][:status], false, get_params_string({ folder: folder }))
  end

  # Stats
  def get_device_statistics
    api_call(ENDPOINTS[:stats][:device])
  end

  def get_folder_statistics
    api_call(ENDPOINTS[:stats][:folder])
  end

  private

  def get_params_string(params_hash)
    str = "?"
    params_hash.each{ |k,v| str << "&#{k}=#{v}" if v}
    str
  end

  def parse_url(endpoint, params = false)
    url_string = @syncthing_url + endpoint[:name]
    url_string << params if params
    uri = URI.parse(url_string)
  end

  def parse_api_response(response)
    if response.code.to_i == 200
      if response.body.nil? || response.body.blank?
        { code: response.code.to_i, message: "Completed successfully"}
      else
        JSON.parse(response.body)
      end
    else
      raise SyncthingApiError.new("#{response.code} #{response.body.to_s}")
    end
  end

  def get_initheader
    {
      'Content-Type' =>'application/json',
      'User-Agent' => 'Syncthing Ruby client',
      'X-API-Key'=>@syncthing_apikey
    }
  end

  def get_call(endpoint, url)
    case endpoint[:method]
    when :post
      Net::HTTP::Post.new(url.request_uri, get_initheader)
    when :get
      Net::HTTP::Get.new(url.request_uri, get_initheader)
    else
      throw "Unknown call method #{endpoint[:method]}"
    end
  end

  def api_call(endpoint, request_body = false, params = false)
    url = parse_url(endpoint, params)

    # construct the call data
    call = get_call(endpoint, url)

    call.body = request_body.to_json

    request = Net::HTTP.new(url.host, url.port)

    request.read_timeout = 30
    usessl = @syncthing_url.match('https')
    request.use_ssl = usessl
    request.verify_mode = OpenSSL::SSL::VERIFY_NONE if usessl

    # make the call
    response = request.start {|http| http.request(call) }

    parse_api_response(response)

  end # api_call

end # module