require 'rubygems' require 'net/http' require 'net/http/post/multipart' require 'zip' require 'thor' require "uri" require 'cgi' require 'active_support/core_ext/object' require 'active_support/core_ext/string' class Project < Thor include Thor::Actions source_root File.join(File.dirname(__FILE__), 'templates') no_commands { def map_device_ip(device_ip) if device_ip.nil? device_ip = '127.0.0.1' begin `adb forward tcp:4000 tcp:4000` rescue Exception=>e puts e.inspect end end device_ip end } desc "proximity [device_ip] [true|false]", "enable/disable proximity refresh" def proximity(device_ip, proximity = 'false') device_ip = map_device_ip(device_ip) url_str = "http://#{device_ip}:4000/control?cmd=proximity&switch=#{proximity}" uri = URI.parse(url_str) # Shortcut response = Net::HTTP.get_response(uri) response.body end desc "launch device IP [URL]","Tell droiuby to connect to app hosted at URL" def launch(device_ip, url) device_ip = map_device_ip(device_ip) url_str = "http://#{device_ip}:4000/control?cmd=launch&url=#{CGI::escape(url)}" puts "loading application at url #{url}" puts url_str uri = URI.parse(url_str) # Shortcut response = Net::HTTP.get_response(uri) # Will print response.body Net::HTTP.get_print(uri) end desc "list [DEVICE IP]","List running app instances" def list(device_ip = nil) device_ip = map_device_ip(device_ip) url_str = "http://#{device_ip}:4000/control?cmd=list" puts url_str uri = URI.parse(url_str) # Shortcut response = Net::HTTP.get_response(uri) result = JSON.parse(response.body) result['list'].split(',').each do |item| puts item end end desc "cmd [command] [DEVICE_IP]", "Send command to a Droiuby instance" def command(command_line, device_ip=nil) device_ip = map_device_ip(device_ip) url_str = "http://#{device_ip}:4000/console?cmd=#{CGI::escape(command_line)}" uri = URI.parse(url_str) # Shortcut response = Net::HTTP.get_response(uri) response.body end desc "reload [DEVICE_IP]", "Reload app at specified device" def reload(device_ip = nil) device_ip = map_device_ip(device_ip) url_str = "http://#{device_ip}:4000/control?cmd=reload" uri = URI.parse(url_str) # Shortcut response = Net::HTTP.get_response(uri) response.body end desc "switch [name] [DEVICE IP]","switch to target app instance identified by name" def switch(name, device_ip = nil) device_ip = map_device_ip(device_ip) url_str = "http://#{device_ip}:4000/control?cmd=switch&name=#{CGI::escape(name)}" puts url_str uri = URI.parse(url_str) # Shortcut response = Net::HTTP.get_response(uri) # Will print response.body response = Net::HTTP.get_print(uri) end desc "autostart MODE [NAME] [DEVICE IP]","set current app to load on startup" def autostart(mode = 'on', name = nil, device_ip = nil) device_ip = map_device_ip(device_ip) url_str = if mode == 'on' "http://#{device_ip}:4000/control?cmd=autostart#{!name.nil? ? "&name=#{CGI::escape(name)}" : ''}" else "http://#{device_ip}:4000/control?cmd=clearautostart" end puts url_str uri = URI.parse(url_str) # Shortcut response = Net::HTTP.get_response(uri) # Will print response.body Net::HTTP.get_print(uri) end desc "create NAME [WORKSPACE_DIR]","create a new droiuby project with NAME" def create(name, output_dir = 'projects') @name = name @description = name @launcher_icon = '' @base_url = '' @main_xml = File.join("app","views","index.xml") if output_dir.blank? output_dir = Dir.pwd end dest_folder = File.join(output_dir,"#{name}") template File.join('ruby','Gemfile.erb'), File.join(dest_folder,"Gemfile") template File.join('ruby','config.droiuby.erb'), File.join(dest_folder,"config.droiuby") template File.join('ruby','gitignore.erb'), File.join(dest_folder,".gitignore") template File.join('ruby','index.xml.erb'), File.join(dest_folder,"app","views","index.xml") template File.join('ruby','application.css.erb'), File.join(dest_folder,"app","views","styles","application.css") template File.join('ruby','index.rb.erb'), File.join(dest_folder,"app","activities","index.rb") empty_directory File.join(dest_folder,"lib") say "running bundle install" Dir.chdir dest_folder `bundle install` bundle end desc "package NAME [WORKSPACE_DIR] [true|false]","package a project" def package(name, source_dir = 'projects', force = "false") src_folder = if name.blank? Dir.pwd else File.join(source_dir, name) end say "compress #{src_folder}" compress(src_folder, force) end require 'webrick' include WEBrick desc "live NAME DEVICE_IP [HOST NAME] [SOURCE]", "Allow live editing of apps by starting a web server and pointing droiuby to the local instance" def live(name, device_ip = nil, host_name = nil, source_dir = 'projects') source_dir_args = source_dir ? source_dir : 'projects' host_name_args = host_name ? host_name : Socket.gethostname src_dir = if name.blank? Dir.pwd else File.join(source_dir_args, name) end device_ip = map_device_ip(device_ip) port = 2000 ready = false Thread.new do while !ready end begin launch(device_ip, "http://#{host_name_args}:#{port}/config.droiuby") rescue Exception=>e `adb shell am start -W -S --activity-clear-top --activity-brought-to-front -n com.droiuby.application/.DroiubyActivity` end end puts "Starting server: http://#{host_name_args}:#{port}" puts "Document root #{src_dir}" server = HTTPServer.new(:Port=>port,:DocumentRoot=> src_dir,:StartCallback => Proc.new { ready = true }) trap("INT"){ server.shutdown } server.start end desc "upload NAME DEVICE_IP [WORKSPACE_DIR]","uploads a droiuby application to target device running droiuby client" def upload(name, device_ip, source_dir = 'projects', framework = false, run = true) source_dir = if name.blank? || framework Dir.pwd else File.join(source_dir, name) end device_ip = map_device_ip(device_ip) src_package = if framework File.join(source_dir,'framework_src','build',"#{name}.zip") elsif !name.blank? File.join(source_dir,name,'build',"#{name}.zip") else name = File.basename(source_dir) File.join(source_dir,'build',"#{name}.zip") end url_str = "http://#{device_ip}:4000/upload" uri = URI.parse(url_str) say "uploading #{src_package} to #{url_str} -> #{uri.host}:#{uri.port}" File.open(src_package) do |zip| params = { "name" => name, "run" => run ? 'true' : 'false', "file" => UploadIO.new(zip, "application/zip", src_package,"content-disposition" => "form-data; name=\"file\"; filename=\"#{File.basename(src_package)}\"\r\n")} params.merge!(framework: 'true') if framework req = Net::HTTP::Post::Multipart.new uri.path, params retries = 0 res = nil while (retries < 3) sleep 1 + retries begin res = Net::HTTP.start(uri.host, uri.port) do |http| http.request(req) end break rescue Errno::EHOSTUNREACH retries += 1 next end end if res && res.code == "200" say_status 'upload', src_package else if res say_status 'upload','res.body', :red else say_status 'upload',"upload failed. cannot connect to #{url_str}", :red end end end end desc "framework DEVICE_IP","updates the droiuby framework using code from framework_src" def framework(device_ip = nil, source_dir = 'framework_src') compress(source_dir, "true", "framework") upload 'framework', device_ip, File.join(Dir.pwd,"framework_src"), true end desc "execute NAME DEVICE_IP [WORKSPACE_DIR]","package and execute a droiuby application to target device running droiuby client" def execute(name, device_ip, source_dir = 'projects') package name, source_dir, "true" upload name, device_ip, source_dir end desc "bundle", "unpack all cached gems" def bundle cache_dir = File.join('vendor','cache') unless Dir.exists?(cache_dir) puts `bundle package --all` end if Dir.exists?(cache_dir) path = cache_dir puts 'watch out for exploding gems' Dir["#{path}/*.gem"].each do |file| say_status 'unpack', file `gem unpack #{file} --target ./vendor` end else say_status 'error', "can't find cache directory /vendor/cache" end end private def compress(path, force = "false", archive_name = nil) path.sub!(%r[/$],'') unless Dir.exists?(File.join(path,'build')) Dir.mkdir(File.join(path,'build')) end archive_name = File.basename(path) if archive_name.nil? archive = File.join(path,'build',"#{archive_name}.zip") if force=='true' || file_collision(archive) FileUtils.rm archive, :force=>true Zip::File.open(archive, Zip::File::CREATE) do |zipfile| Dir.glob("**{,/*/**}/*.*").reject{ |f| f==archive || f.match(/^build/) }.uniq.each do |file| say_status 'adding', file begin zipfile.add(file, file) rescue Zip::ZipEntryExistsError=>e end end end say_status 'create', archive end end end