3 #=======================================================================
5 # File ID: 2285858a-f9f1-11dd-8b2b-000475e441b9
6 # File Library Database
9 # ©opyleft 2008– Øyvind A. Holm <sunny@sunbase.org>
10 # License: GNU General Public License version 2 or later, see end of
11 # file for legal stuff.
12 #=======================================================================
19 use lib
"$ENV{'HOME'}/bin/src/fldb";
28 my $STD_DATABASE = "fldb";
34 'database' => $STD_DATABASE,
51 $progname =~ s/^.*\/(.*?)$/$1/;
52 our $VERSION = "0.3.0";
54 Getopt
::Long
::Configure
("bundling");
57 "add|a" => \
$Opt{'add'},
58 "crc32" => \
$Opt{'crc32'},
59 "database|D=s" => \
$Opt{'database'},
60 "debug" => \
$Opt{'debug'},
61 "description|d=s" => \
$Opt{'description'},
62 "files-from|f=s" => \
$Opt{'files-from'},
63 "help|h" => \
$Opt{'help'},
64 "json|j" => \
$Opt{'json'},
65 "long|l" => \
$Opt{'long'},
66 "postgres" => \
$Opt{'postgres'},
67 "quiet|q+" => \
$Opt{'quiet'},
68 "verbose|v+" => \
$Opt{'verbose'},
69 "version" => \
$Opt{'version'},
70 "xml|x" => \
$Opt{'xml'},
71 "zero|z" => \
$Opt{'zero'},
73 ) || die("$progname: Option error. Use -h for help.\n");
75 $Opt{'debug'} && ($Debug = 1);
76 $Opt{'verbose'} -= $Opt{'quiet'};
77 ($Opt{'postgres'} || $Opt{'xml'}) && ($Opt{'json'} = 0);
78 ($Opt{'json'}+$Opt{'postgres'}+$Opt{'xml'} > 1) && die("$progname: Cannot mix --json, --postgres and --xml options\n");
79 $Opt{'help'} && usage
(0);
80 if ($Opt{'version'}) {
85 my $postgresql_database = $Opt{'database'};
86 my $postgresql_host="localhost";
88 chomp(my $Hostname = `/bin/hostname`); # FIXME
89 if (!valid_utf8
($Hostname)) {
90 $Hostname = latin1_to_utf8
($Hostname);
92 my $safe_hostname = safe_string
($Hostname);
94 my $use_stdin = ($Opt{'files-from'} eq '-') ?
1 : 0;
96 if ($Opt{'postgres'} && $Opt{'add'}) {
97 $dbh = DBI
->connect("DBI:Pg:dbname=$postgresql_database;host=$postgresql_host")
98 or die("connect: På trynet: $!");
101 my $Sql; # How ironic.
103 $Opt{'zero'} && ($/ = "\x00");
105 $Opt{'json'} && print("{\n \"files\":[");
106 $Opt{'xml'} && print("<fldb>\n");
107 if (length($Opt{'files-from'})) {
108 D
("Opt{files-from} = '$Opt{'files-from'}'");
109 if ($use_stdin || open(FP
, "<", $Opt{'files-from'})) {
110 while (my $Filename = $use_stdin ?
<STDIN
> : <FP
>) {
112 process_file
($Filename);
116 msg
(-1, "$Opt{'files-from'}: Cannot open file for read: $!");
124 $Opt{'json'} && print("\n ]\n}\n");
125 $Opt{'xml'} && print("</fldb>\n");
131 my $Filename = shift;
132 D
("process_file('$Filename')");
134 msg
(0, "$Filename: Ignoring non-file");
137 $Sql = add_entry
($Filename);
140 $Opt{'verbose'} && print("$Filename\n");
141 $dbh->do($Sql) || msg
(-1, "$Filename: Cannot INSERT");
143 $Opt{'json'} && $has_printed && print(',');
153 my $Filename = shift;
154 if ($Filename =~ /\0/) {
155 msg
(-1, "$Filename: Ignoring filename containing zero byte. Did you forget the --zero option?");
158 my $safe_filename = safe_string
($Filename);
159 D
("add_entry(\"$Filename\")");
162 if (@stat_array = stat($Filename)) {
164 my ($Dev, $Inode, $Perm, $Nlinks, $Uid, $Gid, $Rdev, $Size,
165 $Atime, $Mtime, $Ctime, $Blksize, $Blocks) = @stat_array;
166 $Mtime = sec_to_string
($Mtime);
167 $Ctime = sec_to_string
($Ctime);
168 D
("Perm før: '$Perm'");
169 $Perm = sprintf("%04o", $Perm & 07777);
170 D
("Perm etter: '$Perm'");
171 my %Sum = checksum
($Filename, $Opt{'crc32'});
176 $crc32_str = $Opt{'crc32'} ?
"<crc32>$Sum{crc32}</crc32> " : "";
177 } elsif ($Opt{'json'}) {
178 $crc32_str = $Opt{'crc32'} ?
"\"crc32\":\"$Sum{crc32}\"" : "";
179 } elsif ($Opt{'postgres'}) {
180 $crc32_str = $Opt{'crc32'} ?
"'$Sum{crc32}'" : "NULL";
182 D
("crc32_str = '$crc32_str'");
184 if (valid_utf8
($safe_filename)) {
185 $latin1_str = $Opt{'postgres'} ?
"FALSE" : "";
188 $latin1_str = '<latin1>1</latin1>';
189 } elsif ($Opt{'json'}) {
190 $latin1_str = '"latin1":"1"';
191 } elsif ($Opt{'postgres'}) {
192 $latin1_str = 'TRUE';
194 $safe_filename = latin1_to_utf8
($safe_filename);
196 D
("latin1_str = '$latin1_str'");
197 my $base_filename = $safe_filename;
198 $base_filename =~ s/^.*\/(.*?)$/$1/;
199 D
("base_filename = '$base_filename'");
201 my $descr_str = length($Opt{'description'})
202 ?
"<descr>" . txt_to_xml
($Opt{'description'}) . "</descr> "
204 D
("descr_str = \"$descr_str\"");
210 "<sha256>%s</sha256> " .
212 "<gitblob>%s</gitblob> " .
215 "<filename>%s</filename> " .
216 "<mtime>%s</mtime> " .
239 "<sha256>%s</sha256> " .
241 "<gitblob>%s</gitblob> " .
244 "<filename>%s</filename> " .
245 "<mtime>%s</mtime> " .
248 "<ctime>%s</ctime> " .
250 "<inode>%s</inode> " .
251 "<links>%s</links> " .
252 "<device>%s</device> " .
253 "<hostname>%s</hostname> " .
256 # "<lastver>%s</lastver> " .
257 # "<nextver>%s</nextver> " .
284 } elsif ($Opt{'json'}) {
286 my $descr_str = length($Opt{'description'})
287 ?
'"descr":"' . txt_to_json
($Opt{'description'}) . '"'
307 length($crc32_str) && push(@json, $crc32_str);
316 length($descr_str) && push(@json, $descr_str);
341 length($latin1_str) && push(@json, $latin1_str);
342 $Retval = "\n {" . join(',', @json) . "}";
343 } elsif ($Opt{'postgres'}) {
344 my $descr_str = length($Opt{'description'})
345 ?
"E'" . safe_sql
($Opt{'description'}) . "'"
347 D
("descr_str = \"$descr_str\"");
350 $Retval = sprintf(<<END,
352 sha256, sha1, gitblob, md5, crc32,
353 size, filename, mtime, perm,
357 '%s', '%s', '%s', '%s', %s,
358 %s, E'%s', '%s', '%s',
363 $Sum{sha256
}, $Sum{sha1
}, $Sum{gitblob
}, $Sum{md5
}, $crc32_str,
364 $Size, $base_filename, $Mtime, $Perm,
371 $Retval = sprintf(<<END,
373 sha256, sha1, gitblob, md5, crc32,
374 size, filename, mtime, perm, descr, ctime,
376 inode, links, device, hostname,
381 '%s', '%s', '%s', '%s', %s,
382 %s, E'%s', '%s', '%s', %s, '%s',
384 %s, %s, E'%s', E'%s',
390 $Sum{sha256
}, $Sum{sha1
}, $Sum{gitblob
}, $Sum{md5
}, $crc32_str,
391 $Size, $base_filename, $Mtime, $Perm, $descr_str, $Ctime,
393 $Inode, $Nlinks, safe_sql
($Dev), $safe_hostname,
401 D
("=== \$Retval \x7B\x7B\x7B\n$Retval=== \x7D\x7D\x7D");
404 msg
(-1, "$Filename: Cannot read file: $!");
409 msg
(-1, "$Filename: Cannot stat file: $!");
421 $Str = txt_to_xml
($Str);
422 } elsif ($Opt{'json'}) {
423 $Str = txt_to_json
($Str);
424 } elsif ($Opt{'postgres'}) {
425 $Str = safe_sql
($Str);
432 # Convert plain text to JSON {{{
434 $Txt =~ s/\\/\\\\/gs;
436 $Txt =~ s/\x08/\\b/gs;
437 $Txt =~ s/\x09/\\t/gs;
438 $Txt =~ s/\x0a/\\n/gs;
439 $Txt =~ s/\x0c/\\f/gs;
440 $Txt =~ s/\x0d/\\r/gs;
441 $Txt =~ s/([\x00-\x1f])/sprintf('\u%04X', ord($1))/gse;
447 # Convert plain text to XML {{{
449 $Txt =~ s/&/&/gs;
457 # Print program version {{{
458 print("$progname $VERSION\n");
463 # Send the help message to stdout {{{
466 if ($Opt{'verbose'}) {
472 Usage: $progname [options] [file [files [...]]]
477 Add file information to database.
479 Also calculate CRC32. Reads the whole file into memory, so it’s not
480 suitable for big files. Maybe fixed in newer Perl versions.
481 -d x, --description x
482 Use x as file description.
486 Read filenames from x. Use - (hyphen) to read list from stdin.
490 Generate JSON output. This is the default output format.
492 Use long format, include local information.
494 Generate SQL for use with Postgres.
496 Be quiet, suppress messages. Can be repeated.
498 Increase level of verbosity. Can be repeated.
500 Print version information.
504 Filenames are separated by a zero byte (\\0x00) instead of newline
505 (\\n). This makes it possible to read files containing newlines.
507 Print debugging messages.
515 # Convert seconds since 1970 to "yyyy-mm-ddThh:mm:ssZ" {{{
516 my ($Seconds) = shift;
517 ($Seconds =~ /^-?(\d*)(\.\d+)?$/) || return(undef);
519 my @TA = gmtime($Seconds);
520 my($DateString) = sprintf("%04u-%02u-%02uT%02u:%02u:%02uZ",
521 $TA[5]+1900, $TA[4]+1, $TA[3],
522 $TA[2], $TA[1], $TA[0]);
528 # Print a status message to stderr based on verbosity level {{{
529 my ($verbose_level, $Txt) = @_;
531 if ($Opt{'verbose'} >= $verbose_level) {
532 print(STDERR
"$progname: $Txt\n");
539 # Plain Old Documentation (POD) {{{
549 [options] [file [files [...]]]
559 =item B<-h>, B<--help>
561 Print a brief help summary.
563 =item B<-v>, B<--verbose>
565 Increase level of verbosity. Can be repeated.
569 Print version information.
573 Print debugging messages.
583 Made by Øyvind A. Holm S<E<lt>sunny@sunbase.orgE<gt>>.
587 Copyleft © Øyvind A. Holm E<lt>sunny@sunbase.orgE<gt>
588 This is free software; see the file F<COPYING> for legalese stuff.
592 This program is free software: you can redistribute it and/or modify it
593 under the terms of the GNU General Public License as published by the
594 Free Software Foundation, either version 2 of the License, or (at your
595 option) any later version.
597 This program is distributed in the hope that it will be useful, but
598 WITHOUT ANY WARRANTY; without even the implied warranty of
599 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
600 See the GNU General Public License for more details.
602 You should have received a copy of the GNU General Public License along
604 If not, see L<http://www.gnu.org/licenses/>.
612 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :