Store client's user agent on Asciicast record

This commit is contained in:
Marcin Kulik 2013-10-11 20:44:49 +02:00
parent e4cb9c0657
commit d74ead2263
10 changed files with 112 additions and 44 deletions

View File

@ -3,7 +3,7 @@ class Api::AsciicastsController < ApplicationController
skip_before_filter :verify_authenticity_token skip_before_filter :verify_authenticity_token
def create def create
asciicast = AsciicastCreator.new.create(params[:asciicast]) asciicast = AsciicastCreator.new.create(params[:asciicast], request.headers)
render :text => asciicast_url(asciicast), :status => :created, render :text => asciicast_url(asciicast), :status => :created,
:location => asciicast :location => asciicast
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e

View File

@ -5,14 +5,12 @@ class AsciicastDecorator < ApplicationDecorator
THUMBNAIL_HEIGHT = 10 THUMBNAIL_HEIGHT = 10
def os def os
return 'unknown' if uname.blank? if user_agent.present?
os_from_user_agent
if uname =~ /Linux/ elsif uname.present?
'Linux' os_from_uname
elsif uname =~ /Darwin/
'OSX'
else else
uname.split(' ', 2)[0] 'unknown'
end end
end end
@ -99,4 +97,27 @@ class AsciicastDecorator < ApplicationDecorator
"%02d:%02d" % [minutes, seconds] "%02d:%02d" % [minutes, seconds]
end 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 end

View File

@ -1,21 +1,23 @@
class AsciicastParams class AsciicastParams
def initialize(input) def initialize(params, headers)
@input = input @params = params
@headers = headers
end end
def to_h def to_h
attributes = { attributes = {
:stdout_data => input[:stdout], :stdout_data => params[:stdout],
:stdout_timing => input[:stdout_timing], :stdout_timing => params[:stdout_timing],
:stdin_data => input[:stdin], :stdin_data => params[:stdin],
:stdin_timing => input[:stdin_timing], :stdin_timing => params[:stdin_timing],
:username => meta['username'], :username => meta['username'],
:duration => meta['duration'], :duration => meta['duration'],
:recorded_at => meta['recorded_at'], :recorded_at => meta['recorded_at'],
:title => meta['title'], :title => meta['title'],
:command => meta['command'], :command => meta['command'],
:shell => meta['shell'], :shell => meta['shell'],
:user_agent => headers['User-Agent'],
:uname => meta['uname'], :uname => meta['uname'],
:terminal_lines => meta['term']['lines'], :terminal_lines => meta['term']['lines'],
:terminal_columns => meta['term']['columns'], :terminal_columns => meta['term']['columns'],
@ -29,10 +31,10 @@ class AsciicastParams
private private
attr_reader :input attr_reader :params, :headers
def meta def meta
@meta ||= JSON.parse(input[:meta].read) @meta ||= JSON.parse(params[:meta].read)
end end
def assign_user_or_token(attributes, meta) def assign_user_or_token(attributes, meta)

View File

@ -1,7 +1,7 @@
class AsciicastCreator class AsciicastCreator
def create(attributes) def create(attributes, headers)
attributes = AsciicastParams.new(attributes).to_h attributes = AsciicastParams.new(attributes, headers).to_h
asciicast = Asciicast.create!(attributes, without_protection: true) asciicast = Asciicast.create!(attributes, without_protection: true)
AsciicastWorker.perform_async(asciicast.id) AsciicastWorker.perform_async(asciicast.id)

View File

@ -0,0 +1,5 @@
class AddUserAgentToAsciicast < ActiveRecord::Migration
def change
add_column :asciicasts, :user_agent, :string
end
end

View File

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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| create_table "asciicasts", force: true do |t|
t.integer "user_id" t.integer "user_id"
@ -40,6 +40,7 @@ ActiveRecord::Schema.define(version: 20130828162232) do
t.boolean "time_compression", default: true, null: false t.boolean "time_compression", default: true, null: false
t.integer "views_count", default: 0, null: false t.integer "views_count", default: 0, null: false
t.string "stdout_frames" t.string "stdout_frames"
t.string "user_agent"
end end
add_index "asciicasts", ["created_at"], name: "index_asciicasts_on_created_at", using: :btree add_index "asciicasts", ["created_at"], name: "index_asciicasts_on_created_at", using: :btree

View File

@ -14,7 +14,8 @@ describe Api::AsciicastsController do
let(:asciicast) { stub_model(Asciicast, :id => 666) } let(:asciicast) { stub_model(Asciicast, :id => 666) }
before do 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 post :create, :asciicast => attributes
end end
@ -33,7 +34,8 @@ describe Api::AsciicastsController do
let(:full_messages) { ['This is invalid'] } let(:full_messages) { ['This is invalid'] }
before do 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)) and_raise(ActiveRecord::RecordInvalid.new(asciicast))
post :create, :asciicast => attributes post :create, :asciicast => attributes
end end
@ -42,7 +44,7 @@ describe Api::AsciicastsController do
expect(response.status).to eq(422) expect(response.status).to eq(422)
end 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 expect(response.body).to be_blank
end end
end end

View File

@ -11,7 +11,38 @@ describe AsciicastDecorator do
describe '#os' do describe '#os' do
let(:method) { :os } let(:method) { :os }
context 'for Linux-like uname' do 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
context 'and the OS is OSX' do
before do
asciicast.user_agent = "asciinema/0.9.7 (Darwin-10.0.0-i386-64bit)"
end
it { should == 'OSX' }
end
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 'when uname is present' do
context "and it's Linux-like" do
before do before do
asciicast.uname = "Linux t430u 3.5.0-18-generic #29-Ubuntu SMP" asciicast.uname = "Linux t430u 3.5.0-18-generic #29-Ubuntu SMP"
end end
@ -19,7 +50,7 @@ describe AsciicastDecorator do
it { should == 'Linux' } it { should == 'Linux' }
end end
context 'for Darwin-like uname' do context "and it's Darwin-like" do
before do before do
asciicast.uname = "Darwin local 10.3.0 Darwin Kernel Version 10.3.0" asciicast.uname = "Darwin local 10.3.0 Darwin Kernel Version 10.3.0"
end end
@ -27,7 +58,7 @@ describe AsciicastDecorator do
it { should == 'OSX' } it { should == 'OSX' }
end end
context 'for other systems' do context "and it's other" do
before do before do
asciicast.uname = "Jola Misio Foo" asciicast.uname = "Jola Misio Foo"
end end
@ -36,17 +67,20 @@ describe AsciicastDecorator do
should == 'Jola' should == 'Jola'
end end
end end
end
context 'when uname is nil' do context 'when user_agent and uname are nil' do
before do before do
asciicast.user_agent = nil
asciicast.uname = nil asciicast.uname = nil
end end
it { should == 'unknown' } it { should == 'unknown' }
end end
context 'when uname is blank string' do context 'when user_agent and uname are a blank string' do
before do before do
asciicast.user_agent = ' '
asciicast.uname = ' ' asciicast.uname = ' '
end end

View File

@ -2,13 +2,14 @@ require 'spec_helper'
describe AsciicastParams do describe AsciicastParams do
let(:asciicast_params) { described_class.new(input) } let(:asciicast_params) { described_class.new(input, headers) }
let(:input) { { let(:input) { {
:meta => meta_file, :meta => meta_file,
:stdout => stdout_data_file, :stdout => stdout_data_file,
:stdout_timing => stdout_timing_file :stdout_timing => stdout_timing_file
} } } }
let(:headers) { { 'User-Agent' => 'asciinema/0.9.7' } }
let(:stdout_data_file) { double('stdout_data_file') } let(:stdout_data_file) { double('stdout_data_file') }
let(:stdout_timing_file) { double('stdout_timing_file') } let(:stdout_timing_file) { double('stdout_timing_file') }
@ -25,6 +26,7 @@ describe AsciicastParams do
:title => 'bashing :)', :title => 'bashing :)',
:command => '/bin/bash', :command => '/bin/bash',
:shell => '/bin/zsh', :shell => '/bin/zsh',
:user_agent => 'asciinema/0.9.7',
:uname => 'Linux 3.9.9-302.fc19.x86_64 #1 SMP ' + :uname => 'Linux 3.9.9-302.fc19.x86_64 #1 SMP ' +
'Sat Jul 6 13:41:07 UTC 2013 x86_64', 'Sat Jul 6 13:41:07 UTC 2013 x86_64',
:terminal_columns => 96, :terminal_columns => 96,

View File

@ -7,13 +7,14 @@ describe AsciicastCreator do
describe '#create' do describe '#create' do
let(:asciicast) { stub_model(Asciicast, id: 666) } let(:asciicast) { stub_model(Asciicast, id: 666) }
let(:input_attrs) { { a: 'A' } } let(:input_attrs) { { a: 'A' } }
let(:headers) { { 'User-Agent' => 'asciinema/0.9.7' } }
let(:prepared_attrs) { { b: 'B' } } let(:prepared_attrs) { { b: 'B' } }
subject { creator.create(input_attrs) } subject { creator.create(input_attrs, headers) }
before do before do
allow(AsciicastParams).to receive(:new). allow(AsciicastParams).to receive(:new).
with(input_attrs) { prepared_attrs } with(input_attrs, headers) { prepared_attrs }
allow(Asciicast).to receive(:create!) { asciicast } allow(Asciicast).to receive(:create!) { asciicast }
end end