/
asciicast.rb
163 lines (128 loc) · 3.88 KB
/
asciicast.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
class Asciicast < ActiveRecord::Base
ORDER_MODES = { date: 'created_at', popularity: 'views_count' }
mount_uploader :stdin_data, StdinDataUploader
mount_uploader :stdin_timing, StdinTimingUploader
mount_uploader :stdout_data, StdoutDataUploader
mount_uploader :stdout_timing, StdoutTimingUploader
mount_uploader :stdout_frames, StdoutFramesUploader
mount_uploader :file, AsciicastUploader
mount_uploader :image, ImageUploader
serialize :snapshot, ActiveSupportJsonProxy
belongs_to :user
has_many :comments, -> { order(:created_at) }, dependent: :destroy
has_many :likes, dependent: :destroy
validates :user, :duration, presence: true
validates :stdout_data, :stdout_timing, presence: true, unless: :file
validates :file, presence: true, unless: :stdout_data
validates :snapshot_at, numericality: { greater_than: 0, allow_blank: true }
validates :terminal_columns, presence: true, numericality: { greater_than: 0, less_than_or_equal_to: 1000 }
validates :terminal_lines, presence: true, numericality: { greater_than: 0, less_than_or_equal_to: 500 }
scope :featured, -> { where(featured: true) }
scope :by_date, -> { order("created_at DESC") }
scope :by_random, -> { order("RANDOM()") }
scope :non_private, -> { where(private: false) }
scope :homepage_latest, -> { non_private.by_date.limit(6).includes(:user) }
scope :homepage_featured, -> { non_private.featured.where("created_at > ?", 1.year.ago).by_random.limit(6).includes(:user) }
before_create :generate_secret_token
def self.find_by_id_or_secret_token!(thing)
if thing.size == 25
find_by_secret_token!(thing)
else
non_private.find(thing)
end
end
def self.cache_key
timestamps = scoped.select(:updated_at).map { |o| o.updated_at.to_i }
Digest::MD5.hexdigest timestamps.join('/')
end
def self.paginate(page, per_page)
page(page).per(per_page)
end
def self.for_category_ordered(category, order, page = nil, per_page = nil)
collection = self
if category == :featured
collection = collection.featured
end
collection = collection.order("#{ORDER_MODES.fetch(order)} DESC")
if page
collection = collection.paginate(page, per_page)
end
collection
end
def width
terminal_columns
end
def height
terminal_lines
end
def title=(value)
value ? super(value.strip[0...255]) : super
end
def command=(value)
value ? super(value.strip[0...255]) : super
end
def self.generate_secret_token
SecureRandom.hex.to_i(16).to_s(36).rjust(25, '0')
end
def to_param
if private?
secret_token
else
id.to_s
end
end
def data
if file.present?
file
else
stdout_frames
end
end
def data_url(options = {})
data.url(options)
end
def download_filename
"asciicast-#{id}.json"
end
def stdout
return @stdout if @stdout
@stdout = Stdout::Buffered.new(get_stdout)
end
def with_terminal
terminal = Terminal.new(terminal_columns, terminal_lines)
yield(terminal)
ensure
terminal.release if terminal
end
def theme
theme_name.presence && Theme.for_name(theme_name)
end
def image_filename
"#{image_hash}.png"
end
def image_stale?
!image.file || (image.file.filename != image_filename)
end
def owner?(user)
user && self.user == user
end
private
def get_stdout
if version == 0
Stdout::MultiFile.new(stdout_data.decompressed_path,
stdout_timing.decompressed_path)
else
Stdout::SingleFile.new(file.absolute_url)
end
end
def image_hash
version = 2 # version of screenshot, increment to force regeneration
input = "#{version}/#{id}/#{snapshot_at}"
Digest::SHA1.hexdigest(input)
end
def generate_secret_token
begin
self.secret_token = self.class.generate_secret_token
end while self.class.exists?(secret_token: secret_token)
end
end