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

@ -3,31 +3,29 @@
# the GNU General Public License as published by the Free Software Foundation, either version
# 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
# author: jake [a\ jakes-mail dot top
# release: 29, Oct 2023
#
# SETATTR -> truncate, utime, chown, chmod ?
#
# release: 29, Oct 2023
use strict;
use warnings;
use 5.010;
use Mojo::UserAgent;
use JSON;
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);
use File::Slurper qw(read_text);
use File::Slurper qw(read_text read_binary write_binary);
use Mojo::Date;
use Getopt::Long;
use Carp::Always;
use Smart::Comments;
use File::Temp qw(tempfile);
use File::Temp qw(tempfile tempdir);
use threads;
use threads::shared;
@ -37,7 +35,7 @@ my $api;
my $mountpoint;
my $config;
GetOptions ("user=s" => \$user,
"pass=s" => \$pass,
"pass=s" => \$pass,
"api=s" => \$api,
"mountpoint=s" => \$mountpoint,
"config=s" => \$config,
@ -52,7 +50,7 @@ or die("Error in command line arguments\n");
my $config_ref = from_json(read_text($config));
if (exists $config_ref->{user}) {
$user = $config_ref->{user} if not $user;
}
}
if (exists $config_ref->{pass}) {
$pass = $config_ref->{pass} if not $pass;
}
@ -87,6 +85,16 @@ my $suppress_list_update = 0;
my $TYPE_DIR = 0040;
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();
get_listing_from_neocities();
@ -99,13 +107,26 @@ sub try_auth_info {
$res = $tx->res;
}
else {
my $tx = $ua->build_tx(GET => 'https://neocities.org/api/info');
my $tx = $ua->build_tx(GET => 'https://neocities.org/api/info');
$tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res;
}
if ($res->is_error) {
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;
}
@ -114,18 +135,10 @@ sub get_listing_from_neocities {
my $ua = Mojo::UserAgent->new;
$ua = $ua->max_redirects(1); # just in case
my ($tx, $res);
my $tx = $ua->build_tx(GET => 'https://neocities.org/api/list');
$tx->req->headers->authorization("Bearer $api");
my $res = $ua->start($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');
$tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res;
}
if ($res->is_success) {
my $known_files = from_json($res->body);
update_known_files($known_files);
@ -138,6 +151,19 @@ sub get_listing_from_neocities {
sub update_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;
for my $e (@{ $known_files->{files} }) {
@ -174,8 +200,8 @@ sub update_known_files {
mode => $mode,
ctime => $times,
size => $size,
fn => undef,
});
$files{$dirs}{$filename}{fn} = $fns{$dirs}{$filename}{fn};
}
$files{'/'}{'.'} = shared_clone({
type => $TYPE_DIR,
@ -240,10 +266,6 @@ sub e_open {
my ($flags, $fileinfo) = @_;
return -ENOENT() unless exists($files{$dirs}{$file});
return -EISDIR() if $files{$dirs}{$file}{type} & 0040;
#my $fh = [ rand() ];
#return (0, $fh);
return 0;
}
@ -258,21 +280,32 @@ sub e_create {
sub e_read {
my ($dirs, $file) = get_path_and_file(shift);
my ($buf, $off, $fh) = @_;
my ($buf, $off, $_fh) = @_;
return -ENOENT() unless exists($files{$dirs}{$file});
my $ua = Mojo::UserAgent->new;
$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;
if ($res->is_success) {
return substr($res->body,$off,$buf);
if (! $files{$dirs}{$file}{fn}) {
my $ua = Mojo::UserAgent->new;
$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;
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);
}
else {
return -77; # EBADFD, file descrpitor in bad state
}
}
else {
return -77; # EBADFD, file descrpitor in bad state
my $body = read_binary($files{$dirs}{$file}{fn});
return substr($body, $off, $buf);
}
return -EINVAL() if $off > length($files{$dirs}{$file}->{cont});
return 0 if $off == length($files{$dirs}{$file}->{cont});
}
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 {
my ($dirs, $file) = get_path_and_file(shift);
my ($buf, $off, $_fh) = @_;
## # e_write
## # $off
## # $fh
return -ENOENT() unless exists($files{$dirs}{$file});
if (! $files{$dirs}{$file}{fn}) {
# 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};
$fh->autoflush( 1 ); # perl doesnt 'print line' until it sees "\n" normally
seek $fh, $off, 0 if $off;
print $fh $buf;
close $fh;
$files{$dirs}{$file}{modified} = 1;
# my $res = write_to_neocities($dirs, $file, $buf);
return length $buf;
}
sub e_flush {
my ($path, $_fh) = @_;
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 $res = write_to_neocities($dirs, $file, $fn, 1);
unlink $files{$dirs}{$file}{fn};
delete $files{$dirs}{$file}{fn};
return res_errno($res, 0);
my $errno = res_errno($res, 0);
if ($errno == 0) {
$files{$dirs}{$file}{modified} = 0; # synchronized so no longer modified
}
return $errno;
}
else {
# ?
return 0;
}
}
@ -324,18 +355,15 @@ sub e_truncate {
my ($dirs, $file) = get_path_and_file($path);
return -ENOENT if ! exists $files{$dirs}{$file};
if ($length == 0) { # truncate entire file
e_write($path, '');
my $res = e_flush($path);
return $res;
if (! $files{$dirs}{$file}{fn}) {
e_read($path);
}
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;
open my $fh, '>', $files{$dirs}{$file}{fn};
truncate $fh, $length;
$files{$dirs}{$file}{modified} = 1;
close $fh;
my $res = e_flush($path);
return $res;
}
sub e_mknod {
@ -349,19 +377,11 @@ sub e_mknod {
sub e_unlink {
my ($dirs, $file) = get_path_and_file(shift);
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 =>
{'filenames[]' => [ "$dirs/$file" ]});
$tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res;
}
my $tx = $ua->build_tx(POST => 'https://neocities.org/api/delete', => {Accept => '*/*'} => form =>
{'filenames[]' => [ "$dirs/$file" ]});
$tx->req->headers->authorization("Bearer $api");
my $res = $ua->start($tx)->res;
$suppress_list_update = 1;
my $errno = res_errno($res, 0);
@ -373,23 +393,7 @@ sub e_unlink {
}
sub e_mkdir {
# so, neocities API doesn't exactly have a '/api/create_dir/'
# 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/)
# making it 'locally' as neocities auto-mkdir when user puts a file
my $path = shift;
my ($dirs, $file) = get_path_and_file($path);
return -EEXIST if exists $files{$dirs}{$file};
@ -412,7 +416,6 @@ sub e_mkdir {
ctime => time(),
size => 4096,
};
# ## %files
return 0;
}
@ -421,24 +424,13 @@ sub e_mkdir {
sub e_rmdir {
my $path = shift;
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 ($tx, $res);
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" ]});
$tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res;
}
my $tx = $ua->build_tx(POST => 'https://neocities.org/api/delete', => {Accept => '*/*'} => form =>
{'filenames[]' => [ "$path" ]});
$tx->req->headers->authorization("Bearer $api");
my $res = $ua->start($tx)->res;
return res_errno($res, 0);
}
@ -451,18 +443,11 @@ sub e_rename {
return -EEXIST if exists $files{$new_dirs}{$new_file};
my $ua = Mojo::UserAgent->new;
my ($tx, $res);
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 });
$tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res;
}
my $tx = $ua->build_tx(POST => 'https://neocities.org/api/rename', => {Accept => '*/*'} => form =>
{ path => $old_path, new_path => $new_path });
$tx->req->headers->authorization("Bearer $api");
my $res = $ua->start($tx)->res;
return res_errno($res, 0);
}
@ -507,7 +492,7 @@ sub write_to_neocities {
defined $is_buf_fn or $is_buf_fn = 0;
my $ua = Mojo::UserAgent->new();
my $asset;
my $asset;
if (! $is_buf_fn) {
$asset = Mojo::Asset::Memory->new->add_chunk($buffer);
}
@ -515,22 +500,15 @@ sub write_to_neocities {
$asset = Mojo::Asset::File->new(path => $buffer);
}
my ($tx, $res);
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 } });
$tx->req->headers->authorization("Bearer $api");
$res = $ua->start($tx)->res;
}
my $tx = $ua->build_tx(POST => 'https://neocities.org/api/upload' =>
{Accept => '*/*'} => form => {"$dirs/$file" => { file => $asset } });
$tx->req->headers->authorization("Bearer $api");
my $res = $ua->start($tx)->res;
undef $asset;
return $res;
}
# If you run the script directly, it will run fusermount, which will in turn
# re-run this script. Hence the funky semantics.
#my ($mountpoint) = "";