Cannot Remove Redefined Warnings
4 direct replies — Read more / Contribute
|
by Sukhster
on May 05, 2025 at 11:41
|
|
|
Hi,
I have recompiled Perl from Source (v5.40.2) with DBI (DBI-1.647.tar.gz) and DBD-Oracle (DBD-Oracle-1.90.tar.gz) - and am now getting redefined errors for the first time.
Was previously on v5.38.0 with DBI (DBI-1.643.tar.gz) and DBD-Oracle (DBD-Oracle-1.83.tar.gz) and didn't face this issue.
I would like to keep warning - and either resolve these warnings, or remove them.
I have tried the following, but to no avail. I still get the defined warnings.
- use warnings qw(-refine);
- no warnings qw(redefine);
- no warnings 'redefine';
Any advice, Ye Great Monks of Perl?
########################
# Declare Modules
########################
use strict;
use warnings;
# Other Modules
use POSIX qw(strftime);
use Time::HiRes qw(time);
use Time::Piece;
use Time::Seconds;
no warnings 'redefine';
use DBI;
use DBD::Oracle qw(:ora_types :ora_fetch_orient :ora_exe_modes);
. . . .
sub connect_to_database($$$)
{
# Declare the variables
my ($db_uid, $db_pwd, $db_sid) = @_;
my $dbh;
my %attribs = (
PrintError => 0,
AutoCommit => 0,
RaiseError => 0
);
$dbh = DBI->connect("DBI:Oracle:".$db_sid, $db_uid, $db_pwd ,
+\%attribs )
or die "ERROR: Can't connect to database ($db_uid\@$d
+b_sid): ".$DBI::errstr."\n";
return $dbh;
}
Subroutine DBI::db::ora_lob_read redefined at /applications/app12345/A
+PP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/DBI
+.pm line 1398.
Subroutine DBI::db::ora_lob_write redefined at /applications/app12345/
+APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/DB
+I.pm line 1398.
Subroutine DBI::db::ora_lob_append redefined at /applications/app12345
+/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/D
+BI.pm line 1398.
Subroutine DBI::db::ora_lob_trim redefined at /applications/app12345/A
+PP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/DBI
+.pm line 1398.
Subroutine DBI::db::ora_lob_length redefined at /applications/app12345
+/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/D
+BI.pm line 1398.
Subroutine DBI::db::ora_lob_chunk_size redefined at /applications/app1
+2345/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-mul
+ti/DBI.pm line 1398.
Subroutine DBI::db::ora_lob_is_init redefined at /applications/app1234
+5/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/
+DBI.pm line 1398.
Subroutine DBI::db::ora_nls_parameters redefined at /applications/app1
+2345/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-mul
+ti/DBI.pm line 1398.
Subroutine DBI::db::ora_can_unicode redefined at /applications/app1234
+5/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/
+DBI.pm line 1398.
Subroutine DBI::db::ora_can_taf redefined at /applications/app12345/AP
+P_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/DBI.
+pm line 1398.
Subroutine DBI::db::ora_db_startup redefined at /applications/app12345
+/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/D
+BI.pm line 1398.
Subroutine DBI::db::ora_db_shutdown redefined at /applications/app1234
+5/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/
+DBI.pm line 1398.
Subroutine DBI::st::ora_fetch_scroll redefined at /applications/app123
+45/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi
+/DBI.pm line 1398.
Subroutine DBI::st::ora_scroll_position redefined at /applications/app
+12345/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-mu
+lti/DBI.pm line 1398.
Subroutine DBI::st::ora_ping redefined at /applications/app12345/APP_H
+OME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/DBI.pm
+line 1398.
Subroutine DBI::st::ora_stmt_type_name redefined at /applications/app1
+2345/APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-mul
+ti/DBI.pm line 1398.
Subroutine DBI::st::ora_stmt_type redefined at /applications/app12345/
+APP_HOME/apps/perl5/lib/site_perl/5.40.2/x86_64-linux-thread-multi/DB
+I.pm line 1398.
|
cpanplus test suite seems to be a bit ruinous
1 direct reply — Read more / Contribute
|
by Intrepid
on May 03, 2025 at 17:01
|
|
|
The Seekers of Perl Wisdom may not be the best place in the known universe for a bug report, but I know the maintainer of the module I am struggling with, bingos, is active here. So here goes.
The test suite for cpanplus is the creature we need to tame.
I am working with cpanplus v0.9916 and on CygwinPerl v5.40.2
First off, Params::Validate reports an error, exact text shown below, in Dist/MM.pm:
Key 'dir' (t/20_CPANPLUS-Dist-MM.t) is of invalid type for 'CPANPLUS::Internals::Utils::_chdir' provided by CPANPLUS::Dist::MM::create at /cygdrive/c/Users/somia/AppData/Local/.cpanp/.cpanplus/5.40.2/build/nDoXKs5RLj/CPANPLUS-0.9916/t/../lib/CPANPLUS/Dist/MM.pm line 610.
I inserted some dumb simple code to check what is being passed, the output is:
----------------------------------------------------------------------------
Args being passed to Params::Validate: dir|t/20_CPANPLUS-Dist-MM.t
----------------------------------------------------------------------------
I will provide a plate of Buffalo chicken wings to the monk or nun that can work out why that error is appearing. ;-) I've looked and looked and cannot figure it out.
The next matter is equally baffling. I have DBD::SQLite installed to my system but the test suite reports skipping test because "SQLite engine not available":
t/031_CPANPLUS-Internals-Source-SQLite.t ...... skipped: SQLite engine not available
t/032_CPANPLUS-Internals-Source-via-sqlite.t .. skipped: SQLite engine not available
Lastly, the tests in the suite themselves seem to be out of order somehow:
Test Summary Report
-------------------
t/20_CPANPLUS-Dist-MM.t (Wstat: 0 Tests: 87 Failed: 77)
Failed tests: 1, 1, 1, 1, 11-83
Parse errors: Plan (1..1) must be at the beginning or end of the TAP output
Tests out of sequence. Found (1) but expected (11)
Tests out of sequence. Found (11) but expected (12)
Tests out of sequence. Found (12) but expected (13)
Tests out of sequence. Found (13) but expected (14)
Displayed the first 5 of 83 TAP syntax errors.
Re-run prove with the -p option to see them all.
Files=20, Tests=1712, 82 wallclock secs ( 0.34 usr 0.47 sys + 20.69 cusr 50.89 csys = 72.40 CPU)
Result: FAIL
Failed 1/20 test programs. 77/1712 subtests failed.
Anyone able to provide suggestions for how to seek and destroy these annoying errors will have my sincere gratitude.
May 03, 2025 at 21:01 UTC
A just machine to make big decisions
Programmed by fellows (and gals) with compassion and vision
We'll be clean when their work is done
We'll be eternally free yes, and eternally young
Donald Fagen —> I.G.Y.
(Slightly modified for inclusiveness)
|
Regex for hostname validation
2 direct replies — Read more / Contribute
|
by hrcerq
on May 01, 2025 at 23:45
|
|
|
Hello again.
I always used naive regexps for hostname validation. But recently I've been
trying to build something more robust and more adherent to related RFCs.
Mostly, I've consulted the following RFCs:
From that I understand that:
- Hostnames might be composed by 1 or more labels (separated by dots)
- Each label may have at most 63 characteres
- Regardless of how many labels there are, it may be at most 255
characters long
- Each label may contain a combination of letters, numbers and hyphens
- No label may begin or end with a hyphen
- Hostnames can't be composed only by numbers
If the hostname is qualified (i.e. there are at least 2 labels), then:
- There may be 2 or more labels
- Last label is a TLD
- TLDs must not be 1 character long or composed only by numbers
- A trailing dot may be present
BTW, consulting RFCs sometimes feels like walking a complex maze full of hidden
traps, because there's always some obscure detail you might overlook.
Things get worse if we consider some hostnames in the wild not adherent to these
rules (e.g. some use underscores, which is valid for DNS, but not when used in
hostnames), and also that there exist internationalized domain names.
I've tested my regex, but chances are, there are corner cases I'm not aware of,
so maybe anyone you might help me find such cases.
This is how I'm doing:
my $hname_re = qr/
^ (?=(?&validchar){1,255}$) (?!\d+$)
(?&label)
(?: (?:\.(?&label))* \.(?&tld) \.? )?
$
(?(DEFINE)
(?<validchar>[a-zA-Z0-9.-])
(?<alnum>[a-zA-Z0-9])
(?<alnumdash>[a-z-A-Z0-9])
(?<label>(?> (?&alnum)
(?: (?&alnumdash){,61}
(?&alnum) )? ) )
(?<tld>(?!(\d+|.)\.?$) (?&label) )
)
/x;
Thanks for any suggestions.
return on_success() or die;
|
Vexing Race Condition
5 direct replies — Read more / Contribute
|
by Maelstrom
on May 01, 2025 at 10:51
|
|
|
I have a long running script that logs it's process to a file and an FCGI that reads said file every 200ms when prompted to by ajax calls and it seems to mostly work but occasionally fails. I believe I've recreated the problem with the following 2 SSCCE's
one.pl
use Fcntl qw (:DEFAULT :flock :seek);
my $file = '/dev/shm/tmpcount.txt';
for (1 .. 180) {
print "$_\n";
&filewrite($file,$_);
sleep 1;
}
sub filewrite {
my ($file,$enc) = @_;
open(my $fh,">", $file) || die "Can't open $file for reading: $!";
my $mode = LOCK_EX;
flock($fh,$mode) or die "Couldn't lock $file for write";
print $fh $enc;
close $fh;
}
two.pl
use Fcntl qw (:DEFAULT :flock :seek);
my $file = '/dev/shm/tmpcount.txt';
my $c;
do {
if (-e $file) {
$c = &fileread($file);
die "C is empty" unless ($c);
print "count is $c \n";
} else { print "File not found\n"; }
} until ($c == 180);
sub fileread {
+ # Read using perl IO
my $file = shift;
open(my $fh, "<", $file) || die "Can't open $file for reading: $!";
my $mode = LOCK_SH;
flock($fh,$mode) or die "couldn't lock";
my $string = do { local $/; <$fh> };
close $fh;
return $string;
}
When I use warn instead of die the script appears to work, as it is it will invariably complain that $c is empty at some point. What I think is happening is one.pl opens the file and truncates it but before it gets to calling flock two.pl swoops in and reads an empty file. I suppose the obvious solution is semaphores but as I'm trying to develop a "safe" file handling library I'm wondering if there's another way to go about it. My first thought was an atomic open and lock with sysopen but apparently linux can't do that. I'm wondering if opening the file readwrite and truncating after flock would solve this or would possible race conditions remain? I'm aware if multiple processes are doing read-mutate-write then a semaphore lock is the only way to go but I'm curious if 1 writing and 1 reading process can work without them.
|
Nested redirect for STDERR to a string buffer within a perl program
2 direct replies — Read more / Contribute
|
by Anonymous Monk
on Apr 30, 2025 at 11:28
|
|
|
Hi Monks,
I need your wisdom on nesting the rebinding of STDERR to a string buffer (one per nesting level). The communication is all within the same perl program. This is not about capturing STDERR from a system() call or a Pipe.
(Example program at the end of this text. Look for handleOuter() below).
Context:
I have to capture the error output of a Perl module in a string buffer, which is already redirecting the error output of a third party module in order to handle the errors.
The inner module follows the example given in the perlopentut man page and my outer code is following the same pattern.
The code tries to do a nested redirection of STDERR. It stores the prior buffers handle in an hash object.
The outer code redirects STDERR to an $outerbuffer around the calls to the first module, which then redirects STDERR to an $innerbuffer again to capture the errors of some other module which prints on STDERR in case of errors.
It returns to the inner module which restores the binding and reacts to the errors in the $innerbuffer and writes its errors to STDERR and returns to the outer code. The outer code then restores its binding of STDERR and expects the error output or the inner module in $outerbuffer.
Problem:
When the inner wrapper restores the binding for STDERR the prior binding of the outer STDERR to $outerbuffer is not restored.
The text written by the inner module to STDERR after restoring the binding is not showing up in the $outerbuffer.
Question:
How can I recursively rebind and restore STDERR?
How can I rebind/restore STDERR in 'restore()' below?
Is there a better way to save the prior handle?
(Look for 'WHAT TO DO HERE INSTEAD' in the code.)
Pseudo code:
redirect STDERR to $outerbuffer in the classic way of perlopentut
call inner module:
print STDERR "INNER BEFORE CAPTURE" # go to $outerbuffer
redirect STDERR to $innerbuffer in the classic way of perlopentut
call code which reports errors on STDERR eg: "FIRST CAPTURE"
# above output goes to $innerbuffer
restore STDERR of $innerbuffer in the classic way of perlopentut
# From here on printing to STDERR should go to $outerbuffer again
# But it does not.
code handling $innerbuffer ...
print STDERR "inner past restore" # should go to $outerbuffer (is
+ missing)
print STDERR "outer past call" # should go to $outerbuffer (is missing
+)
restore STDERR of $outerbuffer in the classic way of perlopentut
# From here on STDERR goes to the standard file handle (fd=2)
#handleOuter() is expected to produce:
####################
#handleOuter() is expected to produce:
OUTER BEFORE CAPTURE
INNER BEFORE CAPTURE
INNER AFTER RESTORE
BUFFER (inner): >>FIRST CAPTURE<<
OUTER AFTER RESTORE
BUFFER (outer):
>>OUTER BEFORE CALL\ninner past restore\nouter past call<<
INNER BEFORE CAPTURE
INNER AFTER RESTORE
BUFFER (inner):
#>>FIRST CAPTURE<<
OUTER AFTER RESTORE
BUFFER (outer):
#>>INNER BEFORE CAPTURE\ninner past restore<<
Here comes the wrapper module embedded in a full program.
#!/usr/bin/perl
# Example code from 'perldoc -f open'
#
# Redirect standard stream STDERR to a buffer and then
# restore the standard stream
# With extension of nested redirections (which does not work!)
###############################################################
package WrapSTDERR;
use strict;
use warnings;
use Carp;
use Data::Dumper;
sub capture;
sub restore;
sub new {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {
'buffer' => "",
'STREAM' => undef,
'state' => 'closed'
};
$self = bless $self, $class;
return $self;
}
sub DESTROY {
my $self = shift;
if ($self->{'state'} eq 'bound') {
$self->restore();
}
}
sub capture {
my $self = shift;
if ($self->{'state'} eq 'bound') {
confess "Cannot bind STDERR again while it is bound,"
." use another wrapper object.";
}
# duplicate STDERR filehandle in $self->{'STREAM'}
open( $self->{'STREAM'}, ">&STDERR") ## no critic
or die "Failed to save STDERR";
## WHAT TO DO HERE INSTEAD?
## How to save the previous stream for restore?
## Can I find out whether STDERR is already bound to a string buffer
# and save the ref to the buffer for rebinding on restore?
$self->{'STREAM'}->print(""); # get rid of silly warning message
close STDERR; # required for open
open( STDERR, '>', \$self->{'buffer'} ) or die "Cannot open STDERR
+: $!";
STDERR->autoflush();
$self->{'state'} = 'bound';
return $self; # for chaining with new
}
sub restore {
my $self = shift;
if (! $self->{'state'} eq 'bound') {
confess "Cannot restore STDERR when it is not bound.";
}
close STDERR; # remove binding to string buffer
# rebind STDERR to previous handle
open( STDERR, ">&", $self->{'STREAM'} )
or die "Failed to restore STDERR";
## WHAT TO DO ABOVE INSTEAD?
## Can I get the buffer to rebind from the STDERR handle in capture an
+d save it?
## How to restore the binding to the buffer of the previous stream (if
+ there is one)?
$self->{'STREAM'}->close();
$self->{'STREAM'}= undef;
$self->{'state'} = 'closed';
my $data = $self->{'buffer'};
return $data;
}
1;
######################################################################
package main;
sub handleInner {
print "INNER BEFORE CAPTURE\n";
my $innerCapture = WrapSTDERR->new()->capture();
print STDERR "FIRST CAPTURE\n"; # to innerbuffer, works
my $buffer = $innerCapture->restore();
chomp $buffer;
print "INNER AFTER RESTORE\n";
print STDERR "inner past restore\n";
# above goes to console or the outerbuffer,
# it fails for outerbuffer when called from handleOuter
print "BUFFER (inner): \n#>>$buffer<<\n";
}
sub handleOuter {
print "OUTER BEFORE CAPTURE\n";
my $outerCapture = WrapSTDERR->new()->capture();
print STDERR "OUTER BEFORE CALL\n"; # to outerbuffer
handleInner();
print STDERR "outer past call\n"; # to outerbuffer
# (It does not go to the buffer in $outerCapture,
# which is the topic of this question)
my $buffer = $outerCapture->restore();
chomp $buffer;
print "OUTER PAST RESTORE\n";
print "BUFFER (outer): \n#>>$buffer<<\n";
}
handleInner();
#prints this:
# INNER BEFORE CAPTURE
# INNER AFTER RESTORE
# INNER PAST RESTORE
# BUFFER (inner):
#>>FIRST CAPTURE<<
print "####################\n";
handleOuter();
#prints this:
# OUTER BEFORE CAPTURE
# INNER BEFORE CAPTURE
# INNER AFTER RESTORE
# BUFFER (inner): >>FIRST CAPTURE<<
# OUTER AFTER RESTORE
# BUFFER (outer):
#>>OUTER BEFORE CALL<<
# Above line is not correct.
#handleOuter() is expected to produce:
# OUTER BEFORE CAPTURE
# INNER BEFORE CAPTURE
# INNER AFTER RESTORE
# BUFFER (inner): >>FIRST CAPTURE<<
# OUTER AFTER RESTORE
# BUFFER (outer):
#>>OUTER BEFORE CALL\ninner past restore\nouter past call<<
|
Initialize variable in BEGIN
5 direct replies — Read more / Contribute
|
by BillKSmith
on Apr 30, 2025 at 09:18
|
|
|
In a previous reply (Re: Use of BEGIN block) , pfaut suggested using BEGIN and END blocks with the Command Switch n . Here is a complete example.
#!perl -n
use strict;
use warnings;
# USAGE: perl np.pl <np.dat
BEGIN{
our $total = 0;
}
our $total;
$total += $_;
END{
our $total;
print $total
}
Note that the package variable $total is in scope in all three blocks because it is declared in each with our. Is there a preferred way to do this? I understand that without strict, no declaration is necessary and with the use vars syntax, there is no restriction on scope.
|
Perl alarm not going off when local websocket connection is active
3 direct replies — Read more / Contribute
|
by jagordon59
on Apr 27, 2025 at 07:08
|
|
|
I have an online game with the game server written in Perl 5.32 hosted on Amazon Linux where players connect from their browser using web sockets. In addition to real players connecting, Bot players can also connect which is via a Perl script on the same server where the game server is running. The game works fine but of course you have to consider cases where a user wants to explicitly quit the game, closes their browser (or tab) or refreshes their browser tab. To deal with these I set an alarm for 1 second whenever getting a disconnect. If a player was just refreshing their browser, they will reconnect immediately cancelling the alarm and all is good. If they were explicitly quitting or closed their browser tab, there will be no reconnect and the alarm should go off and the sub will remove them from the game. This all works fine when it's just real players in a game all connected from their browser. However, when there is one or more bots connected and one of the real players disconnects, the alarm is set but NEVER goes off.
The failure occurs the next time the game server has to send a message out to all connected players. Since the alarm didn't go off, that disconnected player didn't get removed from the list. And when time the game server tries to send a message to that player, it pukes due to a syswrite error on the socket that was internally closed.
I have tried using unsafe signals and POSIX signal handler but neither of these work any differently or better.
$conn->on(
disconnect => sub {
my $disconnectPlayer = $self->{players}->getPlayerWithSocket(
+$conn );
$SIG{ALRM} = sub { $self->playerDisconnected() };
$self->{playerDisconnected} = 1;
$self->{disconnectedPlayerSocket} = $conn;
alarm( 1 );
}
);
So the bottom line is, why would the alarm be affected by having an incoming web socket connection from a process on the local host?
|
what is exactly HANDL is
4 direct replies — Read more / Contribute
|
by Anonymous Monk
on Apr 27, 2025 at 05:11
|
|
|
I'd like to use mmap function in windows, here is the code
use strict;
use warnings;
use Fcntl;
use Inline C => Config => LIBS => '-lkernel32', BUILD_NOISY => 1, type
+maps => 'typemap';
use Inline C => <<'END_OF_C_CODE';
#include <windows.h>
#include <winbase.h>
void*
map_region(int filehandle, DWORD size, DWORD prot){
HANDLE hMap = CreateFileMapping((HANDLE)filehandle, NULL, prot, 0,
+ size, NULL);
LPVOID addr = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, size)
+;
CloseHandle(hMap);
return addr;
}
int
unmap_region(void* addr, DWORD size){
return UnmapViewOfFile(addr);
}
END_OF_C_CODE
my $test_file = 'test_mmap.bin';
sysopen(my $fh, $test_file, O_RDWR|O_CREAT|O_TRUNC)
or die "can't open: $!";
binmode $fh;
my $size = 4096;
my $addr = map_region(fileno($fh), $size, 0x04)
or die "mmap failed!!: $^E ";
my $test_str = "Hello Windows mmap!";
syswrite($fh, $test_str);
unmap_region($addr, $size) or die "fail to unmap";
the code could pass the check, but when I run it, it throws: "mmap failed!!: The handle is invalid at ccc.pl line 29." It seems that the handle I passed from $fh is not correct. My question is what the windows Handle is, and how I get in perl?
Beside:
here is the typemap
BOOL T_IV
LONG T_IV
HANDLE T_IV
HWND T_IV
HMENU T_IV
HKEY T_IV
SECURITY_INFORMATION T_UV
DWORD T_UV
UINT T_UV
REGSAM T_UV
WPARAM T_UV
LPARAM T_IV
LPVOID T_UV
HANDLE T_UV
SIZE_T T_UV
DibSect * O_OBJECT
######################################################################
+#######
INPUT
T_UV
$var = ($type)SvUV($arg)
######################################################################
+#######
OUTPUT
T_UV
sv_setuv($arg, (UV)$var);
|
Use of BEGIN block
6 direct replies — Read more / Contribute
|
by harangzsolt33
on Apr 26, 2025 at 20:47
|
|
|
Okay, I think, I understand what the BEGIN block does. But I don't understand why this feature was added to Perl. When programming in C or JavaScript or BASIC, I never needed a BEGIN block, so I cannot imagine why would one want to use this or what situation would warrant its use. Why is this important to have in Perl? Was this just a nice feature that was added to make the language more versatile, or does it actually solve a real-life problem? I mean can someone show me a real-life scenario where the BEGIN block is a must have, and you cannot live without it?
|
new perl distribution
3 direct replies — Read more / Contribute
|
by pault
on Apr 22, 2025 at 15:06
|
|
|
I am packaging a new perl distribution ( because https://pault.com/perl.html ) and I am looking for perl users, who might want to give it a try. I need to figure out a lot of stuff with current perl situation, so I tried to subscribe to various perl mailing lists that used to work 20 years ago and they simply do not work now. Somehow. Do you guys and gals know, how can I find the active Perl mailing lists? I only need to find a few people who care about perl as a language and would like to use perl for new challenges. Perl is the only language that makes sense to me last few years and I would keep it going. I have 70% done, planning to turn into 100% next few months.
|
|