Newer Older
260 lines | 5.622kb
add files
Yuki Kimoto authored on 2014-03-26
1
package Mojo::Asset::File;
2
use Mojo::Base 'Mojo::Asset';
3

            
4
use Carp 'croak';
5
use Errno 'EEXIST';
6
use Fcntl qw(O_CREAT O_EXCL O_RDWR);
7
use File::Copy 'move';
8
use File::Spec::Functions 'catfile';
9
use IO::File;
10
use Mojo::Util 'md5_sum';
11

            
12
has [qw(cleanup path)];
13
has handle => sub {
14
  my $self = shift;
15

            
16
  # Open existing file
17
  my $handle = IO::File->new;
18
  my $path   = $self->path;
19
  if (defined $path && -f $path) {
20
    $handle->open($path, '<') or croak qq{Can't open file "$path": $!};
21
    return $handle;
22
  }
23

            
24
  # Open new or temporary file
25
  my $base = catfile $self->tmpdir, 'mojo.tmp';
26
  my $name = $path // $base;
27
  until ($handle->open($name, O_CREAT | O_EXCL | O_RDWR)) {
28
    croak qq{Can't open file "$name": $!} if defined $path || $! != $!{EEXIST};
29
    $name = "$base." . md5_sum(time . $$ . rand 9 x 7);
30
  }
31
  $self->path($name);
32

            
33
  # Enable automatic cleanup
34
  $self->cleanup(1) unless defined $self->cleanup;
35

            
36
  return $handle;
37
};
38
has tmpdir => sub { $ENV{MOJO_TMPDIR} || File::Spec::Functions::tmpdir };
39

            
40
sub DESTROY {
41
  my $self = shift;
42
  return unless $self->cleanup && defined(my $path = $self->path);
43
  close $self->handle;
44
  unlink $path if -w $path;
45
}
46

            
47
sub add_chunk {
48
  my ($self, $chunk) = @_;
49

            
50
  my $handle = $self->handle;
51
  $handle->sysseek(0, SEEK_END);
52
  $chunk //= '';
53
  croak "Can't write to asset: $!"
54
    unless defined $handle->syswrite($chunk, length $chunk);
55

            
56
  return $self;
57
}
58

            
59
sub contains {
60
  my ($self, $str) = @_;
61

            
62
  my $handle = $self->handle;
63
  $handle->sysseek($self->start_range, SEEK_SET);
64

            
65
  # Calculate window size
66
  my $end  = $self->end_range // $self->size;
67
  my $len  = length $str;
68
  my $size = $len > 131072 ? $len : 131072;
69
  $size = $end - $self->start_range if $size > $end - $self->start_range;
70

            
71
  # Sliding window search
72
  my $offset = 0;
73
  my $start = $handle->sysread(my $window, $len);
74
  while ($offset < $end) {
75

            
76
    # Read as much as possible
77
    my $diff = $end - ($start + $offset);
78
    my $read = $handle->sysread(my $buffer, $diff < $size ? $diff : $size);
79
    $window .= $buffer;
80

            
81
    # Search window
82
    my $pos = index $window, $str;
83
    return $offset + $pos if $pos >= 0;
84
    $offset += $read;
85
    return -1 if $read == 0 || $offset == $end;
86

            
87
    # Resize window
88
    substr $window, 0, $read, '';
89
  }
90

            
91
  return -1;
92
}
93

            
94
sub get_chunk {
95
  my ($self, $offset, $max) = @_;
96
  $max //= 131072;
97

            
98
  $offset += $self->start_range;
99
  my $handle = $self->handle;
100
  $handle->sysseek($offset, SEEK_SET);
101

            
102
  my $buffer;
103
  if (defined(my $end = $self->end_range)) {
104
    my $chunk = $end + 1 - $offset;
105
    return '' if $chunk <= 0;
106
    $handle->sysread($buffer, $chunk > $max ? $max : $chunk);
107
  }
108
  else { $handle->sysread($buffer, $max) }
109

            
110
  return $buffer;
111
}
112

            
113
sub is_file {1}
114

            
115
sub move_to {
116
  my ($self, $to) = @_;
117

            
118
  # Windows requires that the handle is closed
119
  close $self->handle;
120
  delete $self->{handle};
121

            
122
  # Move file and prevent clean up
123
  my $from = $self->path;
124
  move($from, $to) or croak qq{Can't move file "$from" to "$to": $!};
125
  return $self->path($to)->cleanup(0);
126
}
127

            
128
sub size {
129
  return 0 unless defined(my $file = shift->path);
130
  return -s $file;
131
}
132

            
133
sub slurp {
134
  my $handle = shift->handle;
135
  $handle->sysseek(0, SEEK_SET);
136
  my $content = '';
137
  while ($handle->sysread(my $buffer, 131072)) { $content .= $buffer }
138
  return $content;
139
}
140

            
141
1;
142

            
143
=encoding utf8
144

            
145
=head1 NAME
146

            
147
Mojo::Asset::File - File storage for HTTP content
148

            
149
=head1 SYNOPSIS
150

            
151
  use Mojo::Asset::File;
152

            
153
  # Temporary file
154
  my $file = Mojo::Asset::File->new;
155
  $file->add_chunk('foo bar baz');
156
  say 'File contains "bar"' if $file->contains('bar') >= 0;
157
  say $file->slurp;
158

            
159
  # Existing file
160
  my $file = Mojo::Asset::File->new(path => '/home/sri/foo.txt');
161
  $file->move_to('/yada.txt');
162
  say $file->slurp;
163

            
164
=head1 DESCRIPTION
165

            
166
L<Mojo::Asset::File> is a file storage backend for HTTP content.
167

            
168
=head1 EVENTS
169

            
170
L<Mojo::Asset::File> inherits all events from L<Mojo::Asset>.
171

            
172
=head1 ATTRIBUTES
173

            
174
L<Mojo::Asset::File> inherits all attributes from L<Mojo::Asset> and
175
implements the following new ones.
176

            
177
=head2 cleanup
178

            
179
  my $bool = $file->cleanup;
180
  $file    = $file->cleanup($bool);
181

            
182
Delete file automatically once it's not used anymore.
183

            
184
=head2 handle
185

            
186
  my $handle = $file->handle;
187
  $file      = $file->handle(IO::File->new);
188

            
189
Filehandle, created on demand.
190

            
191
=head2 path
192

            
193
  my $path = $file->path;
194
  $file    = $file->path('/home/sri/foo.txt');
195

            
196
File path used to create L</"handle">, can also be automatically generated if
197
necessary.
198

            
199
=head2 tmpdir
200

            
201
  my $tmpdir = $file->tmpdir;
202
  $file      = $file->tmpdir('/tmp');
203

            
204
Temporary directory used to generate L</"path">, defaults to the value of the
205
MOJO_TMPDIR environment variable or auto detection.
206

            
207
=head1 METHODS
208

            
209
L<Mojo::Asset::File> inherits all methods from L<Mojo::Asset> and implements
210
the following new ones.
211

            
212
=head2 add_chunk
213

            
214
  $file = $file->add_chunk('foo bar baz');
215

            
216
Add chunk of data.
217

            
218
=head2 contains
219

            
220
  my $position = $file->contains('bar');
221

            
222
Check if asset contains a specific string.
223

            
224
=head2 get_chunk
225

            
226
  my $bytes = $file->get_chunk($offset);
227
  my $bytes = $file->get_chunk($offset, $max);
228

            
229
Get chunk of data starting from a specific position, defaults to a maximum
230
chunk size of C<131072> bytes.
231

            
232
=head2 is_file
233

            
234
  my $true = $file->is_file;
235

            
236
True.
237

            
238
=head2 move_to
239

            
240
  $file = $file->move_to('/home/sri/bar.txt');
241

            
242
Move asset data into a specific file and disable L</"cleanup">.
243

            
244
=head2 size
245

            
246
  my $size = $file->size;
247

            
248
Size of asset data in bytes.
249

            
250
=head2 slurp
251

            
252
  my $bytes = $file->slurp;
253

            
254
Read all asset data at once.
255

            
256
=head1 SEE ALSO
257

            
258
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
259

            
260
=cut