From d74ead2263683d4c9090dfd9a3df0135157377ab Mon Sep 17 00:00:00 2001 From: Marcin Kulik Date: Fri, 11 Oct 2013 20:44:49 +0200 Subject: [PATCH] Store client's user agent on Asciicast record --- app/controllers/api/asciicasts_controller.rb | 2 +- app/decorators/asciicast_decorator.rb | 35 ++++++++-- app/models/asciicast_params.rb | 18 ++--- app/services/asciicast_creator.rb | 4 +- ...31011181418_add_user_agent_to_asciicast.rb | 5 ++ db/schema.rb | 3 +- .../api/asciicasts_controller_spec.rb | 8 ++- spec/decorators/asciicast_decorator_spec.rb | 66 ++++++++++++++----- spec/models/asciicast_params_spec.rb | 4 +- spec/services/asciicast_creator_spec.rb | 5 +- 10 files changed, 109 insertions(+), 41 deletions(-) create mode 100644 db/migrate/20131011181418_add_user_agent_to_asciicast.rb diff --git a/app/controllers/api/asciicasts_controller.rb b/app/controllers/api/asciicasts_controller.rb index a264dee..6462a49 100644 --- a/app/controllers/api/asciicasts_controller.rb +++ b/app/controllers/api/asciicasts_controller.rb @@ -3,7 +3,7 @@ class Api::AsciicastsController < ApplicationController skip_before_filter :verify_authenticity_token def create - asciicast = AsciicastCreator.new.create(params[:asciicast]) + asciicast = AsciicastCreator.new.create(params[:asciicast], request.headers) render :text => asciicast_url(asciicast), :status => :created, :location => asciicast rescue ActiveRecord::RecordInvalid => e diff --git a/app/decorators/asciicast_decorator.rb b/app/decorators/asciicast_decorator.rb index a345a8f..d9a1864 100644 --- a/app/decorators/asciicast_decorator.rb +++ b/app/decorators/asciicast_decorator.rb @@ -5,14 +5,12 @@ class AsciicastDecorator < ApplicationDecorator THUMBNAIL_HEIGHT = 10 def os - return 'unknown' if uname.blank? - - if uname =~ /Linux/ - 'Linux' - elsif uname =~ /Darwin/ - 'OSX' + if user_agent.present? + os_from_user_agent + elsif uname.present? + os_from_uname else - uname.split(' ', 2)[0] + 'unknown' end end @@ -99,4 +97,27 @@ class AsciicastDecorator < ApplicationDecorator "%02d:%02d" % [minutes, seconds] end + private + + def os_from_user_agent + match = user_agent.match(/^[^\s]+\s+\(([^\)]+)\)/) + os = match[1].split(/(\s|-)/).first + + guess_os(os) + end + + def os_from_uname + guess_os(uname) + end + + def guess_os(text) + if text =~ /Linux/ + 'Linux' + elsif text =~ /Darwin/ + 'OSX' + else + text.split(' ', 2)[0] + end + end + end diff --git a/app/models/asciicast_params.rb b/app/models/asciicast_params.rb index 9a8a64e..7e56a42 100644 --- a/app/models/asciicast_params.rb +++ b/app/models/asciicast_params.rb @@ -1,21 +1,23 @@ class AsciicastParams - def initialize(input) - @input = input + def initialize(params, headers) + @params = params + @headers = headers end def to_h attributes = { - :stdout_data => input[:stdout], - :stdout_timing => input[:stdout_timing], - :stdin_data => input[:stdin], - :stdin_timing => input[:stdin_timing], + :stdout_data => params[:stdout], + :stdout_timing => params[:stdout_timing], + :stdin_data => params[:stdin], + :stdin_timing => params[:stdin_timing], :username => meta['username'], :duration => meta['duration'], :recorded_at => meta['recorded_at'], :title => meta['title'], :command => meta['command'], :shell => meta['shell'], + :user_agent => headers['User-Agent'], :uname => meta['uname'], :terminal_lines => meta['term']['lines'], :terminal_columns => meta['term']['columns'], @@ -29,10 +31,10 @@ class AsciicastParams private - attr_reader :input + attr_reader :params, :headers def meta - @meta ||= JSON.parse(input[:meta].read) + @meta ||= JSON.parse(params[:meta].read) end def assign_user_or_token(attributes, meta) diff --git a/app/services/asciicast_creator.rb b/app/services/asciicast_creator.rb index 0b1beb0..bb60ee5 100644 --- a/app/services/asciicast_creator.rb +++ b/app/services/asciicast_creator.rb @@ -1,7 +1,7 @@ class AsciicastCreator - def create(attributes) - attributes = AsciicastParams.new(attributes).to_h + def create(attributes, headers) + attributes = AsciicastParams.new(attributes, headers).to_h asciicast = Asciicast.create!(attributes, without_protection: true) AsciicastWorker.perform_async(asciicast.id) diff --git a/db/migrate/20131011181418_add_user_agent_to_asciicast.rb b/db/migrate/20131011181418_add_user_agent_to_asciicast.rb new file mode 100644 index 0000000..1698833 --- /dev/null +++ b/db/migrate/20131011181418_add_user_agent_to_asciicast.rb @@ -0,0 +1,5 @@ +class AddUserAgentToAsciicast < ActiveRecord::Migration + def change + add_column :asciicasts, :user_agent, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 99a97d4..1081670 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20130828162232) do +ActiveRecord::Schema.define(version: 20131011181418) do create_table "asciicasts", force: true do |t| t.integer "user_id" @@ -40,6 +40,7 @@ ActiveRecord::Schema.define(version: 20130828162232) do t.boolean "time_compression", default: true, null: false t.integer "views_count", default: 0, null: false t.string "stdout_frames" + t.string "user_agent" end add_index "asciicasts", ["created_at"], name: "index_asciicasts_on_created_at", using: :btree diff --git a/spec/controllers/api/asciicasts_controller_spec.rb b/spec/controllers/api/asciicasts_controller_spec.rb index b341789..e31a87d 100644 --- a/spec/controllers/api/asciicasts_controller_spec.rb +++ b/spec/controllers/api/asciicasts_controller_spec.rb @@ -14,7 +14,8 @@ describe Api::AsciicastsController do let(:asciicast) { stub_model(Asciicast, :id => 666) } before do - allow(creator).to receive(:create).with(attributes) { asciicast } + allow(creator).to receive(:create). + with(attributes, kind_of(ActionDispatch::Http::Headers)) { asciicast } post :create, :asciicast => attributes end @@ -33,7 +34,8 @@ describe Api::AsciicastsController do let(:full_messages) { ['This is invalid'] } before do - allow(creator).to receive(:create).with(attributes). + allow(creator).to receive(:create). + with(attributes, kind_of(ActionDispatch::Http::Headers)). and_raise(ActiveRecord::RecordInvalid.new(asciicast)) post :create, :asciicast => attributes end @@ -42,7 +44,7 @@ describe Api::AsciicastsController do expect(response.status).to eq(422) end - it 'returns the full error messages as the content body' do + it 'returns nothing as the content body' do expect(response.body).to be_blank end end diff --git a/spec/decorators/asciicast_decorator_spec.rb b/spec/decorators/asciicast_decorator_spec.rb index 7c85022..599dc40 100644 --- a/spec/decorators/asciicast_decorator_spec.rb +++ b/spec/decorators/asciicast_decorator_spec.rb @@ -11,42 +11,76 @@ describe AsciicastDecorator do describe '#os' do let(:method) { :os } - context 'for Linux-like uname' do - before do - asciicast.uname = "Linux t430u 3.5.0-18-generic #29-Ubuntu SMP" + context 'when user_agent is present' do + context 'and the OS is Linux' do + before do + asciicast.user_agent = + "asciinema/0.9.7 " \ + "(Linux-3.8.0-30-generic-x86_64-with-Ubuntu-13.04-raring)" + end + + it { should == 'Linux' } end - it { should == 'Linux' } - end + context 'and the OS is OSX' do + before do + asciicast.user_agent = "asciinema/0.9.7 (Darwin-10.0.0-i386-64bit)" + end - context 'for Darwin-like uname' do - before do - asciicast.uname = "Darwin local 10.3.0 Darwin Kernel Version 10.3.0" + it { should == 'OSX' } end - it { should == 'OSX' } + context 'and the OS is other' do + before do + asciicast.user_agent = "asciinema/0.9.7 (Jola Misio Foo)" + end + + it 'should return first token' do + should == 'Jola' + end + end end - context 'for other systems' do - before do - asciicast.uname = "Jola Misio Foo" + context 'when uname is present' do + context "and it's Linux-like" do + before do + asciicast.uname = "Linux t430u 3.5.0-18-generic #29-Ubuntu SMP" + end + + it { should == 'Linux' } end - it 'should return first token' do - should == 'Jola' + context "and it's Darwin-like" do + before do + asciicast.uname = "Darwin local 10.3.0 Darwin Kernel Version 10.3.0" + end + + it { should == 'OSX' } + end + + context "and it's other" do + before do + asciicast.uname = "Jola Misio Foo" + end + + it 'should return first token' do + should == 'Jola' + end end end - context 'when uname is nil' do + context 'when user_agent and uname are nil' do before do + asciicast.user_agent = nil asciicast.uname = nil end it { should == 'unknown' } end - context 'when uname is blank string' do + context 'when user_agent and uname are a blank string' do before do + asciicast.user_agent = ' ' asciicast.uname = ' ' end diff --git a/spec/models/asciicast_params_spec.rb b/spec/models/asciicast_params_spec.rb index 6b6a9a5..82bf068 100644 --- a/spec/models/asciicast_params_spec.rb +++ b/spec/models/asciicast_params_spec.rb @@ -2,13 +2,14 @@ require 'spec_helper' describe AsciicastParams do - let(:asciicast_params) { described_class.new(input) } + let(:asciicast_params) { described_class.new(input, headers) } let(:input) { { :meta => meta_file, :stdout => stdout_data_file, :stdout_timing => stdout_timing_file } } + let(:headers) { { 'User-Agent' => 'asciinema/0.9.7' } } let(:stdout_data_file) { double('stdout_data_file') } let(:stdout_timing_file) { double('stdout_timing_file') } @@ -25,6 +26,7 @@ describe AsciicastParams do :title => 'bashing :)', :command => '/bin/bash', :shell => '/bin/zsh', + :user_agent => 'asciinema/0.9.7', :uname => 'Linux 3.9.9-302.fc19.x86_64 #1 SMP ' + 'Sat Jul 6 13:41:07 UTC 2013 x86_64', :terminal_columns => 96, diff --git a/spec/services/asciicast_creator_spec.rb b/spec/services/asciicast_creator_spec.rb index 3581705..f13ab88 100644 --- a/spec/services/asciicast_creator_spec.rb +++ b/spec/services/asciicast_creator_spec.rb @@ -7,13 +7,14 @@ describe AsciicastCreator do describe '#create' do let(:asciicast) { stub_model(Asciicast, id: 666) } let(:input_attrs) { { a: 'A' } } + let(:headers) { { 'User-Agent' => 'asciinema/0.9.7' } } let(:prepared_attrs) { { b: 'B' } } - subject { creator.create(input_attrs) } + subject { creator.create(input_attrs, headers) } before do allow(AsciicastParams).to receive(:new). - with(input_attrs) { prepared_attrs } + with(input_attrs, headers) { prepared_attrs } allow(Asciicast).to receive(:create!) { asciicast } end