add caching (to a temp dir) and add relevent code. use API key code over password. remove unneeded comments/clarifed some comments and EOL whitespace

This commit is contained in:
jake 2023-11-06 02:52:42 -05:00
parent 904bcb6f61
commit 6d0ff4d482

View file

@ -11,9 +11,7 @@
# If not, see <https://www.gnu.org/licenses/>. # If not, see <https://www.gnu.org/licenses/>.
# author: jake [a\ jakes-mail dot top # author: jake [a\ jakes-mail dot top
# release: 29, Oct 2023 # release: 29, Oct 2023
#
# SETATTR -> truncate, utime, chown, chmod ?
#
use strict; use strict;
use warnings; use warnings;
use 5.010; use 5.010;
@ -22,12 +20,12 @@ use JSON;
use Fuse qw(fuse_get_context); use Fuse qw(fuse_get_context);
use POSIX qw(ENOENT EISDIR EINVAL EEXIST ENOTEMPTY EACCES EFBIG use POSIX qw(ENOENT EISDIR EINVAL EEXIST ENOTEMPTY EACCES EFBIG
EPERM EBADF ENOSPC EMFILE ENOSYS); EPERM EBADF ENOSPC EMFILE ENOSYS);
use File::Slurper qw(read_text); use File::Slurper qw(read_text read_binary write_binary);
use Mojo::Date; use Mojo::Date;
use Getopt::Long; use Getopt::Long;
use Carp::Always; use Carp::Always;
use Smart::Comments; use Smart::Comments;
use File::Temp qw(tempfile); use File::Temp qw(tempfile tempdir);
use threads; use threads;
use threads::shared; use threads::shared;
@ -87,6 +85,16 @@ my $suppress_list_update = 0;
my $TYPE_DIR = 0040; my $TYPE_DIR = 0040;
my $TYPE_FILE = 0100; my $TYPE_FILE = 0100;
my $tmpdir = tempdir();
END {
for my $dir (keys %files) {
for my $file (keys %{ $files{$dir} }) {
unlink $files{$dir}{$file}{fn} if $files{$dir}{$file}{fn};
}
}
rmdir $tmpdir;
}
die unless try_auth_info(); die unless try_auth_info();
get_listing_from_neocities(); get_listing_from_neocities();
@ -106,6 +114,19 @@ sub try_auth_info {
if ($res->is_error) { if ($res->is_error) {
die "auth pass or api seems to be incorrect."; die "auth pass or api seems to be incorrect.";
} }
else {
# get api key and use that over username + password
# checking if API key is valid is wayyyyy quicker than
# checking if user 'username' exists and hashing the supplied
# password then comparing hashes.
if (! $api) {
my $tx = $ua->get("https://$user:$pass\@neocities.org/api/key");
my $res = $tx->res;
my $body = from_json($res->body);
$api = $body->{api_key};
undef $pass;
}
}
return 1; return 1;
} }
@ -114,17 +135,9 @@ sub get_listing_from_neocities {
my $ua = Mojo::UserAgent->new; my $ua = Mojo::UserAgent->new;
$ua = $ua->max_redirects(1); # just in case $ua = $ua->max_redirects(1); # just in case
my ($tx, $res);
if ($pass) {
$tx = $ua->get("https://$user:$pass\@neocities.org/api/list");
$res = $tx->res;
}
else {
my $tx = $ua->build_tx(GET => 'https://neocities.org/api/list'); my $tx = $ua->build_tx(GET => 'https://neocities.org/api/list');
$tx->req->headers->authorization("Bearer $api"); $tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res; my $res = $ua->start($tx)->res;
}
if ($res->is_success) { if ($res->is_success) {
my $known_files = from_json($res->body); my $known_files = from_json($res->body);
@ -138,6 +151,19 @@ sub get_listing_from_neocities {
sub update_known_files { sub update_known_files {
my ($known_files) = @_; my ($known_files) = @_;
my %fns;
for my $dirs (keys %files) {
for my $file (keys %{ $files{$dirs} }) {
$fns{$dirs}{$file}{fn} = undef;
# autovivication on shared variables can fatally terminate this program
if (exists $files{$dirs} and exists $files{$dirs}
and exists $files{$dirs}{$file} and exists $files{$dirs}{$file}{fn})
{
$fns{$dirs}{$file}{fn} = $files{$dirs}{$file}{fn};
}
}
}
undef %files; undef %files;
for my $e (@{ $known_files->{files} }) { for my $e (@{ $known_files->{files} }) {
@ -174,8 +200,8 @@ sub update_known_files {
mode => $mode, mode => $mode,
ctime => $times, ctime => $times,
size => $size, size => $size,
fn => undef,
}); });
$files{$dirs}{$filename}{fn} = $fns{$dirs}{$filename}{fn};
} }
$files{'/'}{'.'} = shared_clone({ $files{'/'}{'.'} = shared_clone({
type => $TYPE_DIR, type => $TYPE_DIR,
@ -240,10 +266,6 @@ sub e_open {
my ($flags, $fileinfo) = @_; my ($flags, $fileinfo) = @_;
return -ENOENT() unless exists($files{$dirs}{$file}); return -ENOENT() unless exists($files{$dirs}{$file});
return -EISDIR() if $files{$dirs}{$file}{type} & 0040; return -EISDIR() if $files{$dirs}{$file}{type} & 0040;
#my $fh = [ rand() ];
#return (0, $fh);
return 0; return 0;
} }
@ -258,21 +280,32 @@ sub e_create {
sub e_read { sub e_read {
my ($dirs, $file) = get_path_and_file(shift); my ($dirs, $file) = get_path_and_file(shift);
my ($buf, $off, $fh) = @_; my ($buf, $off, $_fh) = @_;
return -ENOENT() unless exists($files{$dirs}{$file}); return -ENOENT() unless exists($files{$dirs}{$file});
if (! $files{$dirs}{$file}{fn}) {
my $ua = Mojo::UserAgent->new; my $ua = Mojo::UserAgent->new;
$ua = $ua->max_redirects(1); # for some reason neocities redirects .html files to not-.html files. $ua = $ua->max_redirects(1); # for some reason neocities redirects .html files to not-.html files.
my $res = $ua->get("https://$user.neocities.org/$dirs/$file")->result; my $res = $ua->get("https://$user.neocities.org/$dirs/$file")->result;
if ($res->is_success) { if ($res->is_success) {
# filehandles CANNOT be shared between threads
(undef, $files{$dirs}{$file}{fn}) = tempfile('neocitiesfs_XXXXXXX', DIR => $tmpdir, UNLINK => 0);
my $fn = $files{$dirs}{$file}{fn};
# this is what write_binary() does but I had issues with it
open my $fh, '>:raw', $fn;
print $fh $res->body;
close $fh;
return substr($res->body,$off,$buf); return substr($res->body,$off,$buf);
} }
else { else {
return -77; # EBADFD, file descrpitor in bad state return -77; # EBADFD, file descrpitor in bad state
} }
}
return -EINVAL() if $off > length($files{$dirs}{$file}->{cont}); else {
return 0 if $off == length($files{$dirs}{$file}->{cont}); my $body = read_binary($files{$dirs}{$file}{fn});
return substr($body, $off, $buf);
}
} }
sub e_statfs { return 255, 1, 1, 1, 1, 2 } sub e_statfs { return 255, 1, 1, 1, 1, 2 }
@ -280,37 +313,35 @@ sub e_statfs { return 255, 1, 1, 1, 1, 2 }
sub e_write { sub e_write {
my ($dirs, $file) = get_path_and_file(shift); my ($dirs, $file) = get_path_and_file(shift);
my ($buf, $off, $_fh) = @_; my ($buf, $off, $_fh) = @_;
## # e_write
## # $off
## # $fh
return -ENOENT() unless exists($files{$dirs}{$file}); return -ENOENT() unless exists($files{$dirs}{$file});
if (! $files{$dirs}{$file}{fn}) { if (! $files{$dirs}{$file}{fn}) {
# filehandles CANNOT be shared between threads # filehandles CANNOT be shared between threads
(undef, $files{$dirs}{$file}{fn}) = tempfile(); (undef, $files{$dirs}{$file}{fn}) = tempfile('neocitiesfs_XXXXXXX', DIR => $tmpdir);
} }
open my $fh, '>>', $files{$dirs}{$file}{fn}; open my $fh, '>>', $files{$dirs}{$file}{fn};
$fh->autoflush( 1 ); # perl doesnt 'print line' until it sees "\n" normally $fh->autoflush( 1 ); # perl doesnt 'print line' until it sees "\n" normally
seek $fh, $off, 0 if $off; seek $fh, $off, 0 if $off;
print $fh $buf; print $fh $buf;
close $fh; close $fh;
$files{$dirs}{$file}{modified} = 1;
# my $res = write_to_neocities($dirs, $file, $buf);
return length $buf; return length $buf;
} }
sub e_flush { sub e_flush {
my ($path, $_fh) = @_; my ($path, $_fh) = @_;
my ($dirs, $file) = get_path_and_file($path); my ($dirs, $file) = get_path_and_file($path);
if ($files{$dirs}{$file}{fn}) { if ($files{$dirs}{$file}{modified} and $files{$dirs}{$file}{modified} == 1) {
my $fn = $files{$dirs}{$file}{fn}; my $fn = $files{$dirs}{$file}{fn};
my $res = write_to_neocities($dirs, $file, $fn, 1); my $res = write_to_neocities($dirs, $file, $fn, 1);
unlink $files{$dirs}{$file}{fn}; my $errno = res_errno($res, 0);
delete $files{$dirs}{$file}{fn}; if ($errno == 0) {
return res_errno($res, 0); $files{$dirs}{$file}{modified} = 0; # synchronized so no longer modified
}
return $errno;
} }
else { else {
# ?
return 0; return 0;
} }
} }
@ -324,19 +355,16 @@ sub e_truncate {
my ($dirs, $file) = get_path_and_file($path); my ($dirs, $file) = get_path_and_file($path);
return -ENOENT if ! exists $files{$dirs}{$file}; return -ENOENT if ! exists $files{$dirs}{$file};
if ($length == 0) { # truncate entire file if (! $files{$dirs}{$file}{fn}) {
e_write($path, ''); e_read($path);
}
open my $fh, '>', $files{$dirs}{$file}{fn};
truncate $fh, $length;
$files{$dirs}{$file}{modified} = 1;
close $fh;
my $res = e_flush($path); my $res = e_flush($path);
return $res; return $res;
} }
else {
e_write($path, e_read($path,0,0), 0, 0);
truncate $files{$dirs}{$file}{fh}, $length;
my $res = e_flush($path);
return $res;
}
return 0;
}
sub e_mknod { sub e_mknod {
my ($dirs, $file) = get_path_and_file(shift); my ($dirs, $file) = get_path_and_file(shift);
@ -349,19 +377,11 @@ sub e_mknod {
sub e_unlink { sub e_unlink {
my ($dirs, $file) = get_path_and_file(shift); my ($dirs, $file) = get_path_and_file(shift);
my $ua = Mojo::UserAgent->new; my $ua = Mojo::UserAgent->new;
my ($tx, $res);
if ($pass) {
$tx = $ua->post("https://$user:$pass\@neocities.org/api/delete", => {Accept => '*/*'} => form =>
{'filenames[]' => [ "$dirs/$file" ]});
$res = $tx->res;
}
else {
my $tx = $ua->build_tx(POST => 'https://neocities.org/api/delete', => {Accept => '*/*'} => form => my $tx = $ua->build_tx(POST => 'https://neocities.org/api/delete', => {Accept => '*/*'} => form =>
{'filenames[]' => [ "$dirs/$file" ]}); {'filenames[]' => [ "$dirs/$file" ]});
$tx->req->headers->authorization("Bearer $api"); $tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res; my $res = $ua->start($tx)->res;
}
$suppress_list_update = 1; $suppress_list_update = 1;
my $errno = res_errno($res, 0); my $errno = res_errno($res, 0);
@ -373,23 +393,7 @@ sub e_unlink {
} }
sub e_mkdir { sub e_mkdir {
# so, neocities API doesn't exactly have a '/api/create_dir/' # making it 'locally' as neocities auto-mkdir when user puts a file
# BUT does create a dir if you upload a file that is in a dir.
# :)
# my ($dirs, $file) = get_path_and_file(shift);
# return -EEXIST if exists $files{$dirs}{$file};
# my $numb = int(rand(99999999)) . 'mkdir_hopefully_no_collsions.html';
#
# $suppress_list_update = 1;
# $res = e_mknod("$dirs/$file/$numb");
#
# $suppress_list_update = 0;
# return res_errno($res,0) if $res != 0;
# return e_unlink("$dirs/$file/$numb");
# or I could just create a directory 'locally' since it is likely the user will put something in it
# (also reduces calls to /api/)
my $path = shift; my $path = shift;
my ($dirs, $file) = get_path_and_file($path); my ($dirs, $file) = get_path_and_file($path);
return -EEXIST if exists $files{$dirs}{$file}; return -EEXIST if exists $files{$dirs}{$file};
@ -412,7 +416,6 @@ sub e_mkdir {
ctime => time(), ctime => time(),
size => 4096, size => 4096,
}; };
# ## %files
return 0; return 0;
} }
@ -421,24 +424,13 @@ sub e_mkdir {
sub e_rmdir { sub e_rmdir {
my $path = shift; my $path = shift;
return -ENOENT if not exists $files{$path}; return -ENOENT if not exists $files{$path};
# commented out for now; causes too many unlink() and get_listing_from_neocities() which just by themselves take a while to complete
#if (not scalar keys %{ $files{$path} } == 2) { # '.' and '..'
# return -ENOTEMPTY;
#}
my $ua = Mojo::UserAgent->new; my $ua = Mojo::UserAgent->new;
my ($tx, $res); my $tx = $ua->build_tx(POST => 'https://neocities.org/api/delete', => {Accept => '*/*'} => form =>
if ($pass) {
$tx = $ua->post("https://$user:$pass\@neocities.org/api/delete", => {Accept => '*/*'} => form =>
{'filenames[]' => [ "$path" ]});
$res = $tx->res;
}
else {
$tx = $ua->build_tx(POST => 'https://neocities.org/api/delete', => {Accept => '*/*'} => form =>
{'filenames[]' => [ "$path" ]}); {'filenames[]' => [ "$path" ]});
$tx->req->headers->authorization("Bearer $api"); $tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res; my $res = $ua->start($tx)->res;
}
return res_errno($res, 0); return res_errno($res, 0);
} }
@ -451,18 +443,11 @@ sub e_rename {
return -EEXIST if exists $files{$new_dirs}{$new_file}; return -EEXIST if exists $files{$new_dirs}{$new_file};
my $ua = Mojo::UserAgent->new; my $ua = Mojo::UserAgent->new;
my ($tx, $res); my $tx = $ua->build_tx(POST => 'https://neocities.org/api/rename', => {Accept => '*/*'} => form =>
if ($pass) {
$tx = $ua->post("https://$user:$pass\@neocities.org/api/rename", => {Accept => '*/*'} => form =>
{ path => $old_path, new_path => $new_path });
$res = $tx->res;
}
else {
$tx = $ua->build_tx(POST => 'https://neocities.org/api/rename', => {Accept => '*/*'} => form =>
{ path => $old_path, new_path => $new_path }); { path => $old_path, new_path => $new_path });
$tx->req->headers->authorization("Bearer $api"); $tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res; my $res = $ua->start($tx)->res;
}
return res_errno($res, 0); return res_errno($res, 0);
} }
@ -515,22 +500,15 @@ sub write_to_neocities {
$asset = Mojo::Asset::File->new(path => $buffer); $asset = Mojo::Asset::File->new(path => $buffer);
} }
my ($tx, $res); my $tx = $ua->build_tx(POST => 'https://neocities.org/api/upload' =>
if ($pass) {
$tx = $ua->post("https://$user:$pass\@neocities.org/api/upload" =>
{Accept => '*/*'} => form => {"$dirs/$file" => { file => $asset } });
$res = $tx->res;
}
else {
$tx = $ua->build_tx(POST => 'https://neocities.org/api/upload' =>
{Accept => '*/*'} => form => {"$dirs/$file" => { file => $asset } }); {Accept => '*/*'} => form => {"$dirs/$file" => { file => $asset } });
$tx->req->headers->authorization("Bearer $api"); $tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res; my $res = $ua->start($tx)->res;
}
undef $asset; undef $asset;
return $res; return $res;
} }
# If you run the script directly, it will run fusermount, which will in turn # If you run the script directly, it will run fusermount, which will in turn
# re-run this script. Hence the funky semantics. # re-run this script. Hence the funky semantics.
#my ($mountpoint) = ""; #my ($mountpoint) = "";