# Copyright (c) 2018 Yegor Bugayenko # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the 'Software'), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. require 'csv' require 'uri' require 'fileutils' # The list of remotes. # Author:: Yegor Bugayenko (yegor256@gmail.com) # Copyright:: Copyright (c) 2018 Yegor Bugayenko # License:: MIT module Zold # All remotes class Remotes PORT = 4096 # Empty, for standalone mode class Empty def all [] end end def initialize(file) @file = file end def all list = load max_score = list.map { |r| r[:score] }.max max_score = 1 if max_score.zero? max_errors = list.map { |r| r[:errors] }.max max_errors = 1 if max_errors.zero? list.sort_by do |r| (1 - r[:errors] / max_errors) * 5 + (r[:score] / max_score) end.reverse end def clean save([]) end def reset FileUtils.mkdir_p(File.dirname(@file)) FileUtils.copy( File.join(File.dirname(__FILE__), '../../resources/remotes'), @file ) end def exists?(host, port = Remotes::PORT) raise 'Port has to be of type Integer' unless port.is_a?(Integer) !load.find { |r| r[:host] == host.downcase && r[:port] == port }.nil? end def add(host, port = Remotes::PORT) raise 'Port has to be of type Integer' unless port.is_a?(Integer) raise 'Port can\'t be negative' if port < 0 raise 'Port can\'t be over 65536' if port > 0xffff raise "#{host}:#{port} alread exists" if exists?(host, port) list = load list << { host: host.downcase, port: port, score: 0 } list.uniq! { |r| "#{r[:host]}:#{r[:port]}" } save(list) end def remove(host, port = Remotes::PORT) raise 'Port has to be of type Integer' unless port.is_a?(Integer) raise "#{host}:#{port} is absent" unless exists?(host, port) list = load list.reject! { |r| r[:host] == host.downcase && r[:port] == port } save(list) end def score(host, port = Remotes::PORT) raise 'Port has to be of type Integer' unless port.is_a?(Integer) raise "#{host}:#{port} is absent" unless exists?(host, port) load.find { |r| r[:host] == host.downcase && r[:port] == port }[:score] end def errors(host, port = Remotes::PORT) raise 'Port has to be of type Integer' unless port.is_a?(Integer) raise "#{host}:#{port} is absent" unless exists?(host, port) load.find { |r| r[:host] == host.downcase && r[:port] == port }[:errors] end def error(host, port = Remotes::PORT) raise 'Port has to be of type Integer' unless port.is_a?(Integer) raise "#{host}:#{port} is absent" unless exists?(host, port) list = load list.find do |r| r[:host] == host.downcase && r[:port] == port end[:errors] += 1 save(list) end def rescore(host, port, score) raise 'Port has to be of type Integer' unless port.is_a?(Integer) raise "#{host}:#{port} is absent" unless exists?(host, port) list = load list.find do |r| r[:host] == host.downcase && r[:port] == port end[:score] = score save(list) end private def load CSV.read(file).map do |r| { host: r[0], port: r[1].to_i, score: r[2].to_i, errors: r[3].to_i, home: URI("http://#{r[0]}:#{r[1]}/") } end end def save(list) File.write( file, list.map do |r| [ r[:host], r[:port], r[:score], r[:errors] ].join(',') end.join("\n") ) end def file reset unless File.exist?(@file) @file end end end