9ac973a9339be265b9473b02f043156a07fe8e2c
[cloudstore.git] / lib / CloudStore / API.pm
1 package CloudStore::API;
2 use warnings;
3 use strict;
4 use autodie;
5
6 use lib 'lib';
7 use base qw(CloudStore::Gearman CloudStore::MD5sum);
8
9 use File::Path qw(make_path remove_tree);
10 use File::Find;
11 use Data::Dump qw(dump);
12
13 sub new {
14         my ($class,$slice) = @_;
15
16         my ( undef, $dir, $port, undef ) = getgrnam($slice);
17         die "can't find group $slice: $!" unless $dir && $port;
18         my $self = {
19                 passwd => '/var/lib/extrausers/passwd',
20                 PORT => $port,
21                 SLICE => $dir,
22         };
23         bless $self, $class;
24
25         $self->{md5} = $self->user_info('md5');
26
27         return $self;
28 }
29
30 sub user_info {
31         my ($self,$login) = @_;
32
33         my @n = qw/ login passwd uid gid quota comment gecos dir shell expire /;
34         my @p = $login =~ m/^\d+$/ ? getpwuid $login : getpwnam $login;
35         my $user;
36         $user->{$_} = shift @p foreach @n;
37         return $user;
38 }
39
40 sub create_user {
41         my ( $self, $new_email, $new_passwd, $new_quota ) = @_;
42
43         my $max_uid = 0;
44         my $found = 0;
45
46         open(my $fh, '<', $self->{passwd});
47         while(<$fh>) {
48                 my ( $login, $passwd, $uid, $gid, $email, $dir, $shell ) = split(/:/,$_);
49                 $max_uid = $uid if $uid > $max_uid;
50                 $found = $uid if $email eq $new_email;
51         }
52         close($fh);
53
54         if ( ! $found ) {
55                 $max_uid++;
56                 my $dir = "$self->{SLICE}/$max_uid";
57                 warn "# create_user $new_email $new_quota = $max_uid $dir";
58                 open(my $fh, '>>', $self->{passwd});
59                 print $fh "u$max_uid:$new_passwd:$max_uid:$self->{PORT}:$new_email:$dir:/bin/true\n";
60                 close($fh);
61                 $found = $max_uid;
62
63                 mkdir $dir;
64                 chown $max_uid, $self->{PORT}, $dir;
65
66                 open($fh, '>', "$dir/.meta/secrets");
67                 print $fh "u$max_uid:$new_passwd\n";
68                 close $fh;
69         }
70
71         # FIXME update quota only on create?
72         $self->gearman_do( 'narada_s1_quota_set' => "$found $new_quota" );
73
74         return $found;
75 }
76
77 sub mkbasepath {
78         my ($self,$path,$opts) = @_;
79         $path =~ s{/[^/]+$}{};
80         make_path $path unless -d $path;
81 }
82
83 sub user_dir {
84         my ( $self,$user, $dir ) = @_;
85         $user = $self->user_info($user) unless ref $user eq 'HASH';
86         my $path;
87         if ( exists $user->{dir} ) {
88                 $path = $user->{dir} . '/.meta/' . $dir;
89         } else {
90                 die "no dir in ", dump $user;
91         }
92         $path =~ s{//+}{/}g;
93
94         if ( ! -e $path ) {
95                 $self->mkbasepath( $path, { uid => $user->{uid} } );
96                 open(my $fh, '>', $path);
97                 close $fh;
98                 chown $user->{uid}, $user->{gid}, $path;
99                 warn "# user_dir created $path\n";
100         }
101
102         warn "## user_dir $path";
103         return $path;
104 }
105
106 sub append {
107         my $self = shift @_;
108         $self->append_meta( 'usage', @_ );
109 }
110
111 sub append_meta {
112         my $self = shift @_;
113         my $log  = shift @_;
114         my $user = shift @_;
115         my $path = $self->user_dir( $user => $log );
116         my $line = join('#',@_);
117         open(my $fh, '>>', $path);
118         print $fh "$line\n";
119         close $fh;
120         warn "## $path $line\n";
121 }
122
123 sub usage {
124         my ( $self, $user ) = @_;
125         $user = $self->user_info($user) unless ref $user eq 'HASH';
126         my $path = $self->user_dir( $user => 'usage');
127         my $sum;
128         open(my $fh, '<', $path);
129         while(<$fh>) {
130                 chomp;
131                 my @v = split(/#/,$_);
132                 $sum->{ $v[0] } += $v[1];
133                 $sum->{_usage}  += $v[1];
134         }
135         my ( $usage, $quota ) = split(/ /,
136                 $self->gearman_do( 'narada_s1_quota_get' => $user->{uid} )
137         );
138         $sum->{_usage} += $usage;
139         $sum->{_quota} = $quota;
140         warn "## usage ",dump($user, $sum), $/;
141         return $sum;
142 }
143
144 sub send_file {
145         my ( $self, $f_uid,$f_path, $t_uid,$t_path ) = @_;
146
147         my $f = $self->user_info($f_uid);
148         my $t = $self->user_info($t_uid);
149
150         my $f_full = "$f->{dir}/$f_path";
151         my $t_full = "$t->{dir}/$t_path";
152
153         $self->mkbasepath( $t_full, { uid => $t->{uid} } );
154
155         my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat $f_full;
156         if ( $uid == $f->{uid} ) {
157                 warn "# send_file - move $f_uid $f_path to pool\n";
158                 chown $self->{md5}->{uid}, $self->{md5}->{gid}, $f_full;
159                 chmod oct("0444"), $f_full;
160                 $self->append( $f, 'sent', -s $f_full, $t->{uid}, $f_path );
161         } elsif ( $uid == $self->{md5}->{uid} ) {
162                 warn "# send_file - shared $f_full\n";
163         }
164
165         $self->delete( $t, $t_path ) if -e $t_full;
166
167         link $f_full, $t_full; 
168         $self->append( $t, 'recv', -s $t_full, $f->{uid}, $t_path );
169 }
170
171 sub rename_file {
172         my ( $self, $user, $from, $to ) = @_;
173         $user = $self->user_info($user) unless ref $user eq 'HASH';
174
175         $self->append( $user, 'rename', $from, $to );
176 }
177
178
179 sub delete {
180         my ( $self, $user, $path ) = @_;
181         $user = $self->user_info($user) unless ref $user eq 'HASH';
182
183         my $deleted_size = 0;
184         my $full_path = "$user->{dir}/$path";
185
186         if ( -d $full_path ) {
187
188                 find({ 
189                 no_chdir => 1,
190                 wanted => sub {
191                         return if -d $_;
192                         my ($uid,$size) = (stat($_))[4,7];
193                         warn "## find $uid $size $_\n";
194                         if ( $uid == $self->{md5}->{uid} ) {
195                                 $deleted_size += $size;
196                         }
197                 }}, $full_path);
198
199                 remove_tree $full_path;
200         } else {
201                 $deleted_size += -s $full_path;
202                 unlink $full_path;
203         }
204
205         warn "delete $deleted_size bytes shared\n";
206
207         $self->append( $user, 'delete', -$deleted_size, $user->{uid}, $path );
208
209         $self->md5sum($user)->out( $path );
210         
211         return $full_path;
212 }
213
214 1;