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