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:
parent
904bcb6f61
commit
6d0ff4d482
1 changed files with 108 additions and 130 deletions
186
neocitiesfs.pl
186
neocitiesfs.pl
|
@ -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) = "";
|
||||||
|
|
Loading…
Reference in a new issue