line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::cpanel; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
59373
|
use Exporter 'import'; |
|
1
|
|
|
|
|
9
|
|
|
1
|
|
|
|
|
60
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.006'; |
6
|
|
|
|
|
|
|
our @EXPORT_OK = qw(dispatch_cmd_print dispatch_cmd_raw_p dir_walk_p); |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 NAME |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
App::cpanel - CLI for cPanel UAPI and API 2 |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=begin markdown |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# PROJECT STATUS |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
[![CPAN version](https://badge.fury.io/pl/App-cpanel.svg)](https://metacpan.org/pod/App::cpanel) |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=end markdown |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 SYNOPSIS |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
$ cpanel uapi Notifications get_notifications_count |
23
|
|
|
|
|
|
|
$ cpanel uapi ResourceUsage get_usages |
24
|
|
|
|
|
|
|
$ cpanel uapi Fileman list_files dir=public_html |
25
|
|
|
|
|
|
|
$ cpanel uapi Fileman get_file_content dir=public_html file=index.html |
26
|
|
|
|
|
|
|
$ cpanel download public_html/index.html |
27
|
|
|
|
|
|
|
$ cpanel api2 Fileman fileop op=chmod metadata=0755 sourcefiles=public_html/cgi-bin/hello-world |
28
|
|
|
|
|
|
|
$ cpanel api2 Fileman fileop op=unlink sourcefiles=public_html/cgi-bin/hello-world |
29
|
|
|
|
|
|
|
$ cpanel api2 Fileman mkdir path= name=new-dir-at-top |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# this one is one at a time but can overwrite files |
32
|
|
|
|
|
|
|
$ cpanel api2 Fileman savefile dir=public_html/cgi-bin filename=hello-world content="$(cat public_html/cgi-bin/hello-world)" |
33
|
|
|
|
|
|
|
# this is multiple files but refuses to overwrite |
34
|
|
|
|
|
|
|
$ cpanel upload public_html/cgi-bin hello-world |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
# download |
37
|
|
|
|
|
|
|
$ cpanel mirror public_html public_html cpanel localfs |
38
|
|
|
|
|
|
|
# upload |
39
|
|
|
|
|
|
|
$ cpanel mirror public_html public_html localfs cpanel |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 DESCRIPTION |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
CLI for cPanel UAPI and also API 2, due to missing functionality in UAPI. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
Stores session token in F<~/.cpanel-token>, a two-line file. First line |
46
|
|
|
|
|
|
|
is the URL component that goes after C. Second is the C |
47
|
|
|
|
|
|
|
cookie, which you can get from your browser's DevTools. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
Stores relevant domain name in F<~/.cpanel-domain>. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head1 FUNCTIONS |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
Exportable: |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=head2 dispatch_cmd_print |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
Will print the return value, using L except for |
58
|
|
|
|
|
|
|
C. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=head2 dispatch_cmd_raw_p |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Returns a promise of the decoded JSON value or Ced content. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head2 dir_walk_p |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Takes C<$from_dir>, C<$to_dir>, C<$from_map>, C<$to_map>. Copies the |
67
|
|
|
|
|
|
|
information in the first directory to the second, using the respective |
68
|
|
|
|
|
|
|
maps. Assumes UNIX-like semantics in filenames, i.e. C<$dir/$file>. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Returns a promise of completion. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
The maps are hash-refs whose values are functions, and the keys are: |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head3 ls |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Takes C<$dir>. Returns a promise of two hash-refs, of directories and of |
77
|
|
|
|
|
|
|
files. Each has keys of relative filename, values are an array-ref |
78
|
|
|
|
|
|
|
containing a string octal number representing UNIX permissions, and a |
79
|
|
|
|
|
|
|
number giving the C. Must reject if does not exist. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=head3 mkdir |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
Takes C<$dir>. Returns a promise of having created the directory. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=head3 read |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Takes C<$dir>, C<$file>. Returns a promise of the file contents. |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=head3 write |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
Takes C<$dir>, C<$file>. Returns a promise of having written the file |
92
|
|
|
|
|
|
|
contents. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=head3 chmod |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
Takes C<$path>, C<$perms>. Returns a promise of having changed the |
97
|
|
|
|
|
|
|
permissions. |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=head1 SEE ALSO |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
L |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
L |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=head1 AUTHOR |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
Ed J |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
112
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=cut |
115
|
|
|
|
|
|
|
|
116
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
18
|
|
117
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
35
|
|
118
|
1
|
|
|
1
|
|
478
|
use Mojo::URL; |
|
1
|
|
|
|
|
174893
|
|
|
1
|
|
|
|
|
9
|
|
119
|
1
|
|
|
1
|
|
627
|
use Mojo::UserAgent; |
|
1
|
|
|
|
|
229358
|
|
|
1
|
|
|
|
|
9
|
|
120
|
1
|
|
|
1
|
|
43
|
use Mojo::File qw(path); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
42
|
|
121
|
1
|
|
|
1
|
|
5
|
use Mojo::Util qw(dumper encode); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2733
|
|
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
my %cmd2func = ( |
124
|
|
|
|
|
|
|
uapi => [ \&uapi_p, 1 ], |
125
|
|
|
|
|
|
|
download => [ \&download_p, 0 ], |
126
|
|
|
|
|
|
|
upload => [ \&upload_p, 1 ], |
127
|
|
|
|
|
|
|
api2 => [ \&api2_p, 1 ], |
128
|
|
|
|
|
|
|
mirror => [ \&mirror_p, 1 ], |
129
|
|
|
|
|
|
|
); |
130
|
|
|
|
|
|
|
my $token_file = "$ENV{HOME}/.cpanel-token"; |
131
|
|
|
|
|
|
|
my $domain_file = "$ENV{HOME}/.cpanel-domain"; |
132
|
|
|
|
|
|
|
my %localfs_map = ( |
133
|
|
|
|
|
|
|
ls => \&localfs_ls, |
134
|
|
|
|
|
|
|
mkdir => \&localfs_mkdir, |
135
|
|
|
|
|
|
|
read => \&localfs_read, |
136
|
|
|
|
|
|
|
write => \&localfs_write, |
137
|
|
|
|
|
|
|
chmod => \&localfs_chmod, |
138
|
|
|
|
|
|
|
); |
139
|
|
|
|
|
|
|
my %cpanel_map = ( |
140
|
|
|
|
|
|
|
ls => \&cpanel_ls, |
141
|
|
|
|
|
|
|
mkdir => \&cpanel_mkdir, |
142
|
|
|
|
|
|
|
read => \&cpanel_read, |
143
|
|
|
|
|
|
|
write => \&cpanel_write, |
144
|
|
|
|
|
|
|
chmod => \&cpanel_chmod, |
145
|
|
|
|
|
|
|
); |
146
|
|
|
|
|
|
|
our %MAP2HASH = ( |
147
|
|
|
|
|
|
|
localfs => \%localfs_map, |
148
|
|
|
|
|
|
|
cpanel => \%cpanel_map, |
149
|
|
|
|
|
|
|
); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub dispatch_cmd_print { |
152
|
0
|
|
|
0
|
1
|
0
|
my $cmd = shift; |
153
|
0
|
0
|
|
|
|
0
|
die "No command\n" unless $cmd; |
154
|
0
|
0
|
|
|
|
0
|
die "Unknown command '$cmd'\n" unless my $info = $cmd2func{$cmd}; |
155
|
0
|
|
|
|
|
0
|
my $p = dispatch_cmd_raw_p($cmd, @_); |
156
|
0
|
0
|
|
|
|
0
|
$p = $p->then(\&dumper) if $info->[1]; |
157
|
0
|
|
|
0
|
|
0
|
$p->then(sub { print @_ }, sub { warn encode 'UTF-8', join '', @_ })->wait; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub dispatch_cmd_raw_p { |
161
|
0
|
|
|
0
|
1
|
0
|
my $cmd = shift; |
162
|
0
|
0
|
|
|
|
0
|
die "No command\n" unless $cmd; |
163
|
0
|
0
|
|
|
|
0
|
die "Unknown command '$cmd'\n" unless my $info = $cmd2func{$cmd}; |
164
|
0
|
|
|
|
|
0
|
goto &{$info->[0]}; |
|
0
|
|
|
|
|
0
|
|
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub api_request { |
168
|
0
|
|
|
0
|
0
|
0
|
my ($method, $domain, $token, $parts, $args, @extra_args) = @_; |
169
|
0
|
|
|
|
|
0
|
my ($url_token, $cookie_token) = split /\s+/, $token; |
170
|
0
|
|
|
|
|
0
|
my $url = Mojo::URL->new("https://$domain:2083"); |
171
|
0
|
|
|
|
|
0
|
$url->path(join '/', '', "cpsess$url_token", @$parts); |
172
|
0
|
0
|
|
|
|
0
|
$url->query(%$args) if $args; |
173
|
0
|
|
|
|
|
0
|
CORE::state $ua = Mojo::UserAgent->new; # state as needs to live long enough to complete request |
174
|
0
|
|
|
|
|
0
|
$ua->$method( |
175
|
|
|
|
|
|
|
$url->to_abs . "", |
176
|
|
|
|
|
|
|
{ Cookie => "cpsession=$cookie_token" }, |
177
|
|
|
|
|
|
|
@extra_args, |
178
|
|
|
|
|
|
|
); |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
sub read_file { |
182
|
0
|
|
|
0
|
0
|
0
|
my ($file) = @_; |
183
|
0
|
0
|
|
|
|
0
|
die "$file: $!\n" unless -f $file; |
184
|
0
|
0
|
|
|
|
0
|
die "$file: too readable\n" if (stat $file)[2] & 0044; |
185
|
0
|
|
|
|
|
0
|
local $/; |
186
|
0
|
0
|
|
|
|
0
|
open my $fh, $file or die "$file: $!\n"; |
187
|
0
|
|
|
|
|
0
|
my $content = <$fh>; |
188
|
0
|
0
|
|
|
|
0
|
die "No content in '$file'\n" unless $content; |
189
|
0
|
|
|
|
|
0
|
$content =~ s/^\s*(.*?)\s*$/$1/g; |
190
|
0
|
|
|
|
|
0
|
$content; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
0
|
|
|
0
|
0
|
0
|
sub read_token { read_file($token_file) } |
194
|
0
|
|
|
0
|
0
|
0
|
sub read_domain { read_file($domain_file) } |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub _error_or_json { |
197
|
0
|
|
|
0
|
|
0
|
my $res = $_[0]->res; |
198
|
0
|
0
|
|
|
|
0
|
die $res->code . ": " . $res->message . "\n" if $res->code != 200; |
199
|
0
|
|
|
|
|
0
|
$res->json; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
sub _uapi_error_or_json { |
203
|
0
|
|
|
0
|
|
0
|
my $json = $_[0]; |
204
|
0
|
0
|
|
|
|
0
|
if (!$json->{status}) { |
205
|
|
|
|
|
|
|
die join '', "Failed:\n", |
206
|
0
|
0
|
|
|
|
0
|
map "$_\n", map @{ $json->{$_} || [] }, qw(errors warnings); |
|
0
|
|
|
|
|
0
|
|
207
|
|
|
|
|
|
|
} |
208
|
0
|
|
|
|
|
0
|
$json; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub uapi_p { |
212
|
0
|
|
|
0
|
0
|
0
|
my ($module, $function, @args) = @_; |
213
|
0
|
0
|
|
|
|
0
|
die "No module\n" unless $module; |
214
|
0
|
0
|
|
|
|
0
|
die "No function\n" unless $function; |
215
|
0
|
|
|
|
|
0
|
my ($token, $domain) = (read_token(), read_domain()); |
216
|
0
|
0
|
|
|
|
0
|
my $args_hash = ref($args[0]) eq 'HASH' |
|
|
0
|
|
|
|
|
|
217
|
|
|
|
|
|
|
? $args[0] |
218
|
|
|
|
|
|
|
: { map split('=', $_, 2), @args } |
219
|
|
|
|
|
|
|
if @args; |
220
|
0
|
|
|
|
|
0
|
my $tx_p = api_request 'get_p', $domain, $token, |
221
|
|
|
|
|
|
|
[ 'execute', $module, $function ], |
222
|
|
|
|
|
|
|
$args_hash; |
223
|
0
|
|
|
|
|
0
|
$tx_p->then(\&_error_or_json)->then(\&_uapi_error_or_json); |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub download_p { |
227
|
0
|
|
|
0
|
0
|
0
|
my ($file) = @_; |
228
|
0
|
0
|
|
|
|
0
|
die "No file\n" unless $file; |
229
|
0
|
|
|
|
|
0
|
my ($token, $domain) = (read_token(), read_domain()); |
230
|
0
|
|
|
|
|
0
|
my $tx_p = api_request 'get_p', $domain, $token, |
231
|
|
|
|
|
|
|
[ 'download' ], |
232
|
|
|
|
|
|
|
{ skipencode => 1, file => $file }; |
233
|
|
|
|
|
|
|
$tx_p->then(sub { |
234
|
0
|
|
|
0
|
|
0
|
my $res = $_[0]->res; |
235
|
0
|
0
|
|
|
|
0
|
die $res->code . ": " . $res->message . "\n" if $res->code != 200; |
236
|
0
|
|
|
|
|
0
|
$res->body; |
237
|
0
|
|
|
|
|
0
|
}); |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub make_upload_form { |
241
|
0
|
|
|
0
|
0
|
0
|
my $dir = shift; |
242
|
0
|
|
|
|
|
0
|
my $counter = 0; |
243
|
|
|
|
|
|
|
+{ |
244
|
|
|
|
|
|
|
dir => $dir, |
245
|
|
|
|
|
|
|
map { |
246
|
0
|
|
|
|
|
0
|
my $p = path $_; |
|
0
|
|
|
|
|
0
|
|
247
|
0
|
|
|
|
|
0
|
('file-' . ++$counter => { |
248
|
|
|
|
|
|
|
filename => $p->basename, |
249
|
|
|
|
|
|
|
content => $p->slurp, |
250
|
|
|
|
|
|
|
}); |
251
|
|
|
|
|
|
|
} @_, |
252
|
|
|
|
|
|
|
}; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
sub upload_p { |
256
|
0
|
|
|
0
|
0
|
0
|
my ($dir, @files) = @_; |
257
|
0
|
0
|
|
|
|
0
|
die "No dir\n" unless $dir; |
258
|
0
|
0
|
|
|
|
0
|
die "No files\n" unless @files; |
259
|
0
|
|
|
|
|
0
|
my ($token, $domain) = (read_token(), read_domain()); |
260
|
0
|
|
|
|
|
0
|
my $tx_p = api_request 'post_p', $domain, $token, |
261
|
|
|
|
|
|
|
[ 'execute', 'Fileman', 'upload_files' ], |
262
|
|
|
|
|
|
|
undef, |
263
|
|
|
|
|
|
|
form => make_upload_form($dir, @files), |
264
|
|
|
|
|
|
|
; |
265
|
0
|
|
|
|
|
0
|
$tx_p->then(\&_error_or_json)->then(\&_uapi_error_or_json); |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
sub _api2_error_or_json { |
269
|
0
|
|
|
0
|
|
0
|
my $json = $_[0]; |
270
|
0
|
|
|
|
|
0
|
my $result = $json->{cpanelresult}; |
271
|
0
|
0
|
0
|
|
|
0
|
if (!$result or !$result->{event}{result} or $result->{error}) { |
|
|
|
0
|
|
|
|
|
272
|
|
|
|
|
|
|
die join '', "Failed:\n", |
273
|
|
|
|
|
|
|
map "$_\n", |
274
|
|
|
|
|
|
|
($result->{error} ? $result->{error} : ()), |
275
|
0
|
0
|
|
|
|
0
|
(map "$_->{src}: $_->{err}", grep !$_->{result}, @{$result->{data} || []}), |
|
0
|
0
|
|
|
|
0
|
|
276
|
|
|
|
|
|
|
; |
277
|
|
|
|
|
|
|
} |
278
|
0
|
|
|
|
|
0
|
$json; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub api2_p { |
282
|
0
|
|
|
0
|
0
|
0
|
my ($module, $function, @args) = @_; |
283
|
0
|
0
|
|
|
|
0
|
die "No module\n" unless $module; |
284
|
0
|
0
|
|
|
|
0
|
die "No function\n" unless $function; |
285
|
0
|
|
|
|
|
0
|
my ($token, $domain) = (read_token(), read_domain()); |
286
|
0
|
0
|
|
|
|
0
|
my $args_hash = ref($args[0]) eq 'HASH' |
|
|
0
|
|
|
|
|
|
287
|
|
|
|
|
|
|
? $args[0] |
288
|
|
|
|
|
|
|
: { map split('=', $_, 2), @args } |
289
|
|
|
|
|
|
|
if @args; |
290
|
|
|
|
|
|
|
my $tx_p = api_request 'post_p', $domain, $token, |
291
|
|
|
|
|
|
|
[ qw(json-api cpanel) ], |
292
|
|
|
|
|
|
|
{ |
293
|
|
|
|
|
|
|
cpanel_jsonapi_module => $module, |
294
|
|
|
|
|
|
|
cpanel_jsonapi_func => $function, |
295
|
|
|
|
|
|
|
cpanel_jsonapi_apiversion => 2, |
296
|
0
|
0
|
|
|
|
0
|
%{ $args_hash || {} }, |
|
0
|
|
|
|
|
0
|
|
297
|
|
|
|
|
|
|
}; |
298
|
0
|
|
|
|
|
0
|
$tx_p->then(\&_error_or_json)->then(\&_api2_error_or_json); |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
sub dir_walk_p { |
302
|
3
|
|
|
3
|
1
|
494
|
my ($from_dir, $to_dir, $from_map, $to_map) = @_; |
303
|
3
|
|
|
|
|
4
|
my $from_dir_perms; |
304
|
|
|
|
|
|
|
$to_map->{ls}->($to_dir)->catch(sub { |
305
|
|
|
|
|
|
|
# only create if ls fails |
306
|
3
|
|
|
3
|
|
1369
|
$to_map->{mkdir}->($to_dir) |
307
|
|
|
|
|
|
|
})->then(sub { |
308
|
3
|
|
|
3
|
|
1107
|
$from_map->{ls}->(path($from_dir)->dirname) |
309
|
|
|
|
|
|
|
})->then(sub { |
310
|
3
|
|
|
3
|
|
1129
|
my ($dirs, $files) = @_; |
311
|
3
|
|
50
|
|
|
9
|
$from_dir_perms = $dirs->{path($from_dir)->basename}[0] || '0755'; |
312
|
|
|
|
|
|
|
})->then(sub { |
313
|
3
|
|
|
3
|
|
568
|
$to_map->{chmod}->($to_dir, $from_dir_perms) |
314
|
|
|
|
|
|
|
})->then(sub { |
315
|
3
|
|
|
3
|
|
882
|
$from_map->{ls}->($from_dir) |
316
|
|
|
|
|
|
|
})->then(sub { |
317
|
3
|
|
|
3
|
|
763
|
my ($dirs, $files) = @_; |
318
|
3
|
|
|
|
|
19
|
my @dir_create_p = map |
319
|
|
|
|
|
|
|
dir_walk_p("$from_dir/$_", "$to_dir/$_", $from_map, $to_map), |
320
|
|
|
|
|
|
|
sort keys %$dirs; |
321
|
|
|
|
|
|
|
my @file_create_p = map { |
322
|
3
|
|
|
|
|
337
|
my $this_file = $_; |
|
3
|
|
|
|
|
168
|
|
323
|
|
|
|
|
|
|
$from_map->{read}->($from_dir, $this_file) |
324
|
3
|
|
|
|
|
488
|
->then(sub { $to_map->{write}->($to_dir, $this_file, $_[0]) }) |
325
|
3
|
|
|
|
|
780
|
->then(sub { $to_map->{chmod}->("$to_dir/$this_file", $files->{$this_file}[0]) }) |
326
|
3
|
|
|
|
|
8
|
} sort keys %$files; |
327
|
3
|
100
|
|
|
|
367
|
return Mojo::Promise->resolve(1) unless @dir_create_p + @file_create_p; |
328
|
2
|
|
|
|
|
7
|
Mojo::Promise->all(@dir_create_p, @file_create_p); |
329
|
3
|
|
|
|
|
11
|
}); |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub mirror_p { |
333
|
0
|
|
|
0
|
0
|
|
my ($from_dir, $to_dir, $from_map, $to_map) = @_; |
334
|
0
|
0
|
|
|
|
|
die "No from_dir\n" unless $from_dir; |
335
|
0
|
0
|
|
|
|
|
die "No to_dir\n" unless $to_dir; |
336
|
0
|
0
|
|
|
|
|
die "No from_map\n" unless $from_map; |
337
|
0
|
0
|
|
|
|
|
die "No to_map\n" unless $to_map; |
338
|
0
|
0
|
|
|
|
|
die "Invalid from_map\n" unless $from_map = $MAP2HASH{$from_map}; |
339
|
0
|
0
|
|
|
|
|
die "Invalid to_map\n" unless $to_map = $MAP2HASH{$to_map}; |
340
|
0
|
|
|
|
|
|
dir_walk_p $from_dir, $to_dir, $from_map, $to_map; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
sub localfs_ls { |
344
|
0
|
|
|
0
|
0
|
|
my ($dir) = @_; |
345
|
0
|
|
|
|
|
|
my $dir_path = path($dir); |
346
|
|
|
|
|
|
|
my %files = map { |
347
|
0
|
|
|
|
|
|
($_->basename => [ sprintf("%04o", $_->lstat->mode & 07777), $_->lstat->mtime ]) |
|
0
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
} $dir_path->list({hidden => 1})->each; |
349
|
|
|
|
|
|
|
my %dirs = map { |
350
|
0
|
|
|
|
|
|
($_->basename => [ sprintf("%04o", $_->lstat->mode & 07777), $_->lstat->mtime ]) |
351
|
0
|
|
|
0
|
|
|
} $dir_path->list({dir => 1, hidden => 1})->grep(sub { !$files{$_->basename} })->each; |
|
0
|
|
|
|
|
|
|
352
|
0
|
|
|
|
|
|
Mojo::Promise->resolve(\%dirs, \%files); |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
sub localfs_mkdir { |
356
|
0
|
|
|
0
|
0
|
|
my ($dir) = @_; |
357
|
0
|
|
|
|
|
|
my $dir_path = path($dir); |
358
|
0
|
|
|
|
|
|
$dir_path->make_path; |
359
|
0
|
|
|
|
|
|
Mojo::Promise->resolve(1); |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
sub localfs_read { |
363
|
0
|
|
|
0
|
0
|
|
my ($dir, $file) = @_; |
364
|
0
|
|
|
|
|
|
my $path = path($dir)->child($file); |
365
|
0
|
|
|
|
|
|
Mojo::Promise->resolve($path->slurp); |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
sub localfs_write { |
369
|
0
|
|
|
0
|
0
|
|
my ($dir, $file, $content) = @_; |
370
|
0
|
|
|
|
|
|
my $path = path($dir)->child($file); |
371
|
0
|
|
|
|
|
|
$path->spurt($content); |
372
|
0
|
|
|
|
|
|
Mojo::Promise->resolve(1); |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
sub localfs_chmod { |
376
|
0
|
|
|
0
|
0
|
|
my ($path, $perms) = @_; |
377
|
0
|
|
|
|
|
|
$path = path($path); |
378
|
0
|
|
|
|
|
|
$path->chmod(oct $perms); |
379
|
0
|
|
|
|
|
|
Mojo::Promise->resolve(1); |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
sub cpanel_ls { |
383
|
0
|
|
|
0
|
0
|
|
my ($dir) = @_; |
384
|
|
|
|
|
|
|
uapi_p(qw(Fileman list_files), { dir => $dir })->then(sub { |
385
|
0
|
|
|
0
|
|
|
my (%dirs, %files); |
386
|
|
|
|
|
|
|
($_->{type} eq 'dir' ? \%dirs : \%files)->{$_->{file}} = |
387
|
|
|
|
|
|
|
[ $_->{nicemode}, $_->{mtime} ] |
388
|
0
|
0
|
|
|
|
|
for @{ $_[0]->{data} }; |
|
0
|
|
|
|
|
|
|
389
|
0
|
|
|
|
|
|
(\%dirs, \%files); |
390
|
0
|
|
|
|
|
|
}); |
391
|
|
|
|
|
|
|
} |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
sub cpanel_read { |
394
|
0
|
|
|
0
|
0
|
|
my ($dir, $file) = @_; |
395
|
0
|
|
|
|
|
|
download_p "$dir/$file"; |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
sub cpanel_mkdir { |
399
|
0
|
|
|
0
|
0
|
|
my ($dir) = @_; |
400
|
0
|
|
|
|
|
|
$dir = path $dir; |
401
|
0
|
|
|
|
|
|
api2_p qw(Fileman mkdir), { path => $dir->dirname, name => $dir->basename }; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
sub cpanel_write { |
405
|
0
|
|
|
0
|
0
|
|
my ($dir, $file, $content) = @_; |
406
|
0
|
|
|
|
|
|
api2_p qw(Fileman savefile), { |
407
|
|
|
|
|
|
|
dir => $dir, filename => $file, content => $content, |
408
|
|
|
|
|
|
|
}; |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub cpanel_chmod { |
412
|
0
|
|
|
0
|
0
|
|
my ($path, $perms) = @_; |
413
|
0
|
|
|
|
|
|
api2_p qw(Fileman fileop), { |
414
|
|
|
|
|
|
|
op => 'chmod', metadata => $perms, sourcefiles => $path, |
415
|
|
|
|
|
|
|
}; |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
1; |