File Coverage

blib/lib/Dancer2/Core/Role/SessionFactory/File.pm
Criterion Covered Total %
statement 56 67 83.5
branch 15 32 46.8
condition 0 3 0.0
subroutine 14 15 93.3
pod 0 1 0.0
total 85 118 72.0


line stmt bran cond sub pod time code
1             package Dancer2::Core::Role::SessionFactory::File;
2             # ABSTRACT: Role for file-based session factories
3             $Dancer2::Core::Role::SessionFactory::File::VERSION = '2.1.0';
4 6     6   335977 use Moo::Role;
  6         16745  
  6         49  
5             with 'Dancer2::Core::Role::SessionFactory';
6              
7 6     6   4334 use Carp 'croak';
  6         16  
  6         449  
8 6     6   523 use Dancer2::Core::Types;
  6         22  
  6         53  
9 6     6   84707 use Dancer2::FileUtils qw(escape_filename);
  6         24  
  6         497  
10 6     6   47 use Fcntl ':flock';
  6         12  
  6         913  
11 6     6   1990 use File::Copy ();
  6         19632  
  6         142  
12 6     6   36 use Path::Tiny ();
  6         12  
  6         5970  
13              
14             #--------------------------------------------------------------------------#
15             # Required by classes consuming this role
16             #--------------------------------------------------------------------------#
17              
18             requires '_suffix'; # '.yml', '.json', etc.
19             requires '_thaw_from_handle'; # given handle, return session 'data' field
20             requires '_freeze_to_handle'; # given handle and data, serialize it
21              
22              
23             #--------------------------------------------------------------------------#
24             # Attributes and methods
25             #--------------------------------------------------------------------------#
26              
27             has session_dir => (
28             is => 'ro',
29             isa => Str,
30             default => sub { Path::Tiny::path( '.', 'sessions' )->stringify },
31             );
32              
33             has _session_dir_path => (
34             is => 'ro',
35             lazy => 1,
36             builder => '_build_session_dir_path',
37             init_arg => undef,
38             );
39              
40             sub _build_session_dir_path {
41 4     4   38 my $self = shift;
42 4         63 return Path::Tiny::path( $self->session_dir );
43             }
44              
45             sub BUILD {
46 4     4 0 321 my $self = shift;
47              
48 4 100       79 if ( !$self->_session_dir_path->is_dir ) {
49 1 50       569 mkdir $self->session_dir
50             or croak "Unable to create session dir : "
51             . $self->session_dir . ' : '
52             . $!;
53             }
54             }
55              
56             sub _sessions {
57 0     0   0 my ($self) = @_;
58 0         0 my $sessions = [];
59              
60 0 0       0 opendir( my $dh, $self->session_dir )
61             or croak "Unable to open directory " . $self->session_dir . " : $!";
62              
63 0         0 my $suffix = $self->_suffix;
64              
65 0         0 while ( my $file = readdir($dh) ) {
66 0 0 0     0 next if $file eq '.' || $file eq '..';
67 0 0       0 if ( $file =~ /(\w+)\Q$suffix\E/ ) {
68 0         0 push @{$sessions}, $1;
  0         0  
69             }
70             }
71 0         0 closedir($dh);
72              
73 0         0 return $sessions;
74             }
75              
76             sub _retrieve {
77 17     17   51 my ( $self, $id ) = @_;
78 17         377 my $session_file = $self->_session_dir_path->child(
79             escape_filename($id) . $self->_suffix,
80             )->stringify;
81              
82 17 100       2311 croak "Invalid session ID: $id" unless -f $session_file;
83              
84 16 50       902 open my $fh, '+<', $session_file or die "Can't open '$session_file': $!\n";
85 16 50       229 flock $fh, LOCK_SH or die "Can't lock file '$session_file': $!\n";
86 16         100 my $data = $self->_thaw_from_handle($fh);
87 16 50       71216 close $fh or die "Can't close '$session_file': $!\n";
88              
89 16         195 return $data;
90             }
91              
92             sub _change_id {
93 1     1   7 my ($self, $old_id, $new_id) = @_;
94              
95 1         29 my $old_path = $self->_session_dir_path->child(
96             escape_filename($old_id) . $self->_suffix
97             )->stringify;
98              
99 1 50       125 return if !-f $old_path;
100              
101 1         32 my $new_path = $self->_session_dir_path->child(
102             escape_filename($new_id) . $self->_suffix
103             )->stringify;
104              
105 1         64 File::Copy::move($old_path, $new_path);
106             }
107              
108             sub _destroy {
109 5     5   16 my ( $self, $id ) = @_;
110 5         111 my $session_file = $self->_session_dir_path->child(
111             escape_filename($id) . $self->_suffix
112             )->stringify;
113 5 50       543 return if !-f $session_file;
114              
115 5         888 unlink $session_file;
116             }
117              
118             sub _flush {
119 21     21   410 my ( $self, $id, $data ) = @_;
120 21         449 my $session_file = $self->_session_dir_path->child(
121             escape_filename($id) . $self->_suffix
122             )->stringify;
123              
124 21 50       5695 open my $fh, '>', $session_file or die "Can't open '$session_file': $!\n";
125 21 50       308 flock $fh, LOCK_EX or die "Can't lock file '$session_file': $!\n";
126 21 50       156 seek $fh, 0, 0 or die "Can't seek in file '$session_file': $!\n";
127 21 50       747 truncate $fh, 0 or die "Can't truncate file '$session_file': $!\n";
128 21     4   458 binmode $fh, ':encoding(UTF-8)';
  4         3066  
  4         65  
  4         27  
129 21         5880 $self->_freeze_to_handle( $fh, $data );
130 21 50       3385 close $fh or die "Can't close '$session_file': $!\n";
131              
132 21         254 return $data;
133             }
134              
135             1;
136              
137             __END__