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
238
neocitiesfs.pl
238
neocitiesfs.pl
|
@ -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) = "";
|
||||
|
|
Loading…
Reference in a new issue