require 'redis' require 'cgi' require 'json' require 'base64' require 'zip/zip' require 'yaml' require 'rhoconnect/license' require 'rhoconnect/version' require 'rhoconnect/document' require 'rhoconnect/lock_ops' require 'rhoconnect/model' require 'rhoconnect/source' require 'rhoconnect/store' require 'rhoconnect/stats/record' require 'rhoconnect/stats/middleware' require 'rhoconnect/user' require 'rhoconnect/api_token' require 'rhoconnect/app' require 'rhoconnect/client' require 'rhoconnect/read_state' require 'rhoconnect/client_sync' require 'rhoconnect/source_adapter' require 'rhoconnect/source_sync' require 'rhoconnect/rho_indifferent_access' require 'rhoconnect/jobs/source_job' require 'rhoconnect/jobs/ping_job' require 'rhoconnect/bulk_data' require 'rhoconnect/db_adapter' require 'rhoconnect/dynamic_adapter' REDIS_URL = 'REDIS' unless defined? REDIS_URL # Various module utilities for the store module Rhoconnect APP_NAME = 'application' unless defined? APP_NAME class InvalidArgumentError < RuntimeError; end class RhoconnectServerError < RuntimeError; end # Used by application authenticate to indicate login failure class LoginException < RuntimeError; end extend self class << self attr_accessor :base_directory, :app_directory, :data_directory, :vendor_directory, :blackberry_bulk_sync, :redis, :environment, :log_disabled, :license, :bulk_sync_poll_interval, :stats, :appserver, :api_token, :raise_on_expired_lock, :lock_duration, :cookie_expire, :predefined_sources end ### Begin Rhoconnect setup methods # Server hook to initialize Rhoconnect def bootstrap(basedir) config = get_config(basedir) #Load environment environment = (ENV['RHO_ENV'] || ENV['RACK_ENV'] || :development).to_sym # Initialize Rhoconnect and Resque Rhoconnect.base_directory = basedir Rhoconnect.app_directory = get_setting(config,environment,:app_directory) Rhoconnect.data_directory = get_setting(config,environment,:data_directory) Rhoconnect.vendor_directory = get_setting(config,environment,:vendor_directory) Rhoconnect.blackberry_bulk_sync = get_setting(config,environment,:blackberry_bulk_sync,false) Rhoconnect.bulk_sync_poll_interval = get_setting(config,environment,:bulk_sync_poll_interval,3600) Rhoconnect.redis = get_setting(config,environment,:redis,false) Rhoconnect.appserver ||= get_setting(config,environment,:appserver,false) Rhoconnect.api_token = ENV['API_TOKEN'] || get_setting(config,environment,:api_token,false) Rhoconnect.log_disabled = get_setting(config,environment,:log_disabled,false) Rhoconnect.raise_on_expired_lock = get_setting(config,environment,:raise_on_expired_lock,false) Rhoconnect.lock_duration = get_setting(config,environment,:lock_duration) Rhoconnect.environment = environment Rhoconnect.cookie_expire = get_setting(config,environment,:cookie_expire) || 31536000 Rhoconnect.predefined_sources = {} yield self if block_given? Store.create(Rhoconnect.redis) Resque.redis = Store.db Rhoconnect.base_directory ||= File.join(File.dirname(__FILE__),'..') Rhoconnect.app_directory ||= Rhoconnect.base_directory Rhoconnect.data_directory ||= File.join(Rhoconnect.base_directory,'data') Rhoconnect.vendor_directory ||= File.join(Rhoconnect.base_directory,'vendor') Rhoconnect.stats ||= false Rhoconnect.license = License.new check_and_add(File.join(Rhoconnect.app_directory,'sources')) start_app(config) create_admin_user check_hsql_lib! if Rhoconnect.blackberry_bulk_sync end def start_app(config) if config and config[Rhoconnect.environment] app = nil app_name = APP_NAME if App.is_exist?(app_name) app = App.load(app_name) else app = App.create(:name => app_name) end sources = config[:sources] || [] Source.delete_all app.delete_sources sources.each do |source_name,fields| source_config = source_config(source_name) check_for_schema_field!(source_config) source_config[:name] = source_name Source.create(source_config,{:app_id => app.name}) app.sources << source_name # load ruby file for source adapter to re-load class load under_score(source_name+'.rb') end # load all pre-defined adapters files Dir[File.join(File.dirname(__FILE__),'rhoconnect','predefined_adapters','*.rb')].each { |adapter| load adapter } # Create associations for all sources Source.update_associations(app.sources) end end # Generate admin user on first load def create_admin_user unless User.is_exist?('rhoadmin') admin = User.create({:login => 'rhoadmin', :admin => 1}) admin.password = ENV['PSWRD'] || '' admin.create_token end end # Add path to load_path unless it has been added already def check_and_add(path) $:.unshift path unless $:.include?(path) end def get_config(basedir) # Load settings settings_file = File.join(basedir,'settings','settings.yml') if basedir YAML.load_file(settings_file) if settings_file and File.exist?(settings_file) end def source_config(source_name) source_config = {} sources = Rhoconnect.get_config(Rhoconnect.base_directory)[:sources] source_config = sources[source_name] unless (sources.nil? or sources[source_name].nil?) env_config = Rhoconnect.get_config(Rhoconnect.base_directory)[Rhoconnect.environment] force_default = source_config[:force_default] source_config.delete(:force_default) # apply global env settings [:poll_interval].each do |setting| def_setting = env_config["#{setting.to_s}_default".to_sym] next unless def_setting if source_config[setting].nil? or force_default source_config[setting] = def_setting end end source_config end ### End Rhoconnect setup methods def register_predefined_source(source_name) return if Rhoconnect.predefined_sources.has_key?(source_name) Rhoconnect.predefined_sources[source_name] = {:source_loaded => false} end def create_predefined_source(source_name,params) source_data = Rhoconnect.predefined_sources[source_name] return unless source_data if source_data[:source_loaded] != true source_config = Rhoconnect.source_config(source_name) source_config[:name] = source_name Source.create(source_config,params) app = App.load(Rhoconnect::APP_NAME) app.sources << source_name source_data[:source_loaded] = true end end def check_default_secret!(secret) if secret == '' log "*"*60+"\n\n" log "WARNING: Change the session secret in config.ru from to something secure." log " i.e. running `rake rhoconnect:secret` in your rhoconnect app directory will generate a secret you could use.\n\n" log "*"*60 end end def check_for_schema_field!(fields) if fields['schema'] log "ERROR: 'schema' field in settings.yml is not supported anymore, please use source adapter schema method!" exit(1) end end # Serializes oav to set element def setelement(obj,attrib,value) #"#{obj}:#{attrib}:#{Base64.encode64(value.to_s)}" "#{obj}:#{attrib}:#{value.to_s}" end # De-serializes oav from set element def getelement(element) res = element.split(':',3) #[res[0], res[1], Base64.decode64(res[2].to_s)] [res[0], res[1], res[2]] end # Get random UUID string def get_random_uuid UUIDTools::UUID.random_create.to_s.gsub(/\-/,'') end # Generates new token (64-bit integer) based on # of # microseconds since Jan 1 2009 def get_token ((Time.now.to_f - Time.mktime(2009,"jan",1,0,0,0,0).to_f) * 10**6).to_i end # Computes token for a single client request def compute_token(doc_key) token = get_token Store.put_value(doc_key,token) token.to_s end # Returns require-friendly filename for a class def under_score(camel_cased_word) camel_cased_word.to_s.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr("-", "_"). downcase end # Taken from rails inflector def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) if first_letter_in_uppercase lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } end end def check_hsql_lib! unless File.exists?(File.join(Rhoconnect.vendor_directory,'hsqldata.jar')) log "*"*60 log "" log "WARNING: Missing vendor/hsqldata.jar, please install it for BlackBerry bulk sync support." log "" log "*"*60 end end def expire_bulk_data(username, partition = :user) name = BulkData.get_name(partition,username) data = BulkData.load(name) data.refresh_time = Time.now.to_i if data end def unzip_file(file_dir,params) uploaded_file = File.join(file_dir, params[:filename]) begin File.open(uploaded_file, 'wb') do |file| file.write(params[:tempfile].read) end Zip::ZipFile.open(uploaded_file) do |zip_file| zip_file.each do |f| f_path = File.join(file_dir,f.name) FileUtils.mkdir_p(File.dirname(f_path)) zip_file.extract(f, f_path) { true } end end rescue Exception => e log "Failed to unzip `#{uploaded_file}`" raise e ensure FileUtils.rm_f(uploaded_file) end end def lap_timer(msg,start) duration = timenow - start log "#{msg}: #{duration}" timenow end def start_timer(msg='starting') log "#{msg}" timenow end def timenow (Time.now.to_f * 1000) end def log(*args) now = Time.now.strftime('%I:%M:%S %p %Y-%m-%d') puts "[#{now}] #{args.join}" unless Rhoconnect.log_disabled end # Base rhoconnect application class class Base # Add everything in vendor to load path # TODO: Integrate with 3rd party dependency management def self.initializer(path=nil) Dir["vendor/*"].each do |dir| $:.unshift File.join(dir,'lib') end require 'rhoconnect' require 'rhoconnect/server' # Bootstrap Rhoconnect system Rhoconnect.bootstrap(path || ENV['PWD']) end def self.store_blob(obj,field_name,blob) blob[:tempfile].path if blob[:tempfile] end end protected def get_setting(config,environment,setting,default=nil) res = nil res = config[environment][setting] if config and environment res || default end end