CGI/Perl Guide | Learning Center | Forums | Advertise | Login
Site Search: in

  Main Index MAIN
INDEX
Search Posts SEARCH
POSTS
Who's Online WHO'S
ONLINE
Log in LOG
IN

Home: Perl Programming Help: Beginner:
subroutines

 



terry1738
Novice

Mar 9, 2009, 8:06 PM

Post #1 of 24 (4265 views)
subroutines Can't Post

Hi Illustrious ones,

I am as lowly as they come in the art of Perl, however I decided I will rewrite my old bash accounts program into Perl and PHP.

I have the accounts program up and running in Perl and PHP but because I cannot get my head around passing variables to and from subroutines in Perl it is currently very clunky. How clunky?-well read on.

Here is my problem. I have many plans for my customers which involve deducing the cost of each plan based on the amount of megabytes used.

I have a file for each plan containing the maths and I pass the megabytes used by the customer by putting the number of Megabytes used into a file and getting the plan to read the file.

I then calculate the retail cost, wholesale cost, extracost, profit of each plan.

I then return the name of the plan, wholesale cost, retail cost, extracost and the profit margin to yet another file and get the main program to read the file and use the results to bill the clients. Which is about as clunky as you can get.

Here is an example of the math structure of these plans (I currently have around 50 different plans).

$plancost = 29.95;
if ( $outdata < 201 ) { #if Mb <201 no extracost
$extracost = 0;
}

if (($outdata > 200) && ($outdata < 267)) {
$extracost = (($outdata - 200)*.15); # if Mb between 200 and 266 15c/Mb
}

if (($outdata > 266) && ($outdata < 501)) {
$extracost = 10; #over 266 and <501 extracost $10
}

if (($outdata > 500) && ($outdata < 1001)) {
$extracost = 20;
}

if ($outdata > 1000) {
$extracost = 30;
}

$totalcost = $plancost + $extracost;
$plandesc = "ADSL 256/64 Varible";

$acost = 28.60;
$extrac = ($outdata * .0055);
$wcost = ($acost + $extrac);
$profit = ($totalcost - $wcost);
}

I have done a fair bit of research but cannot solve how to pass the Megabytes used to the plan-file and the get the data back using subroutines.

All I need is an outline to get me going in the right direction in the following areas, as I think I have enough understanding to fill in the details.

The best way to pass the Megabyte data from the main program to the plan-file subroutine.

How to pass the Megabyte data to the maths in the plan-file subroutine.

How to pack up the results from the plan-file subroutine in an array and send it back to the main program.

How to get the data out of the array when its returned to the main program.

I apologise for my stupidity in advance but I am old and somewhat lacking in available brain cells.

Regards Terry


KevinR
Veteran


Mar 9, 2009, 9:08 PM

Post #2 of 24 (4260 views)
Re: [terry1738] subroutines [In reply to] Can't Post

see if this helps you figure it out:

http://perldoc.perl.org/perlsub.html
-------------------------------------------------


terry1738
Novice

Mar 9, 2009, 10:50 PM

Post #3 of 24 (4259 views)
Re: [KevinR] subroutines [In reply to] Can't Post

Thanks Kevin for the reply.

That tutorial is exactly the same as the book programing Perl and thats what prompted me to ask the question here in the first place

I was hoping to get a more direct path to the answer as I am struggling with Perl let alone trying to make a comparison to C as this sentance from the tutorial does.

because a lexical variable is lexically (also called statically) scoped to its enclosing block, eval, or do FILE, this doesn't mean that within a function it works like a C static. It normally works more like a C auto, but with implicit garbage collection.


KevinR
Veteran


Mar 9, 2009, 11:39 PM

Post #4 of 24 (4257 views)
Re: [terry1738] subroutines [In reply to] Can't Post


In Reply To
Thanks Kevin for the reply.

That tutorial is exactly the same as the book programing Perl and thats what prompted me to ask the question here in the first place

I was hoping to get a more direct path to the answer as I am struggling with Perl let alone trying to make a comparison to C as this sentance from the tutorial does.

because a lexical variable is lexically (also called statically) scoped to its enclosing block, eval, or do FILE, this doesn't mean that within a function it works like a C static. It normally works more like a C auto, but with implicit garbage collection.


Well, I am not sure if you are simply asking how to pass arguments and return results or if you are asking how to use static variables with perl. Passing in arguments to subroutines and returning results is straight forward and simple. Using static variables is accomplished in perl 5.10 using the "state" function. You can also create static varaibles using "my" and enclosing brackets:


Code
{ 
my $static_var = 1;
sub foo {
my ($arg) = @_;
....
$static_var++;
....
return(whatever)
}
}


In the above $static_var is initialized before the subroutine "foo" but can be changed as needed inside the "foo" function, but it is invisible to the rest of the program.
-------------------------------------------------


terry1738
Novice

Mar 10, 2009, 12:38 AM

Post #5 of 24 (4254 views)
Re: [KevinR] subroutines [In reply to] Can't Post

GDay Kevin
Strange how as soon as you put a question to a forum you work out the answer or part of it anyway.

I called my plan-file apc.pm
These are the elements I had in my main program
use apc;
then I called the function with the data
plancontroler("$outdatamb");

then in apc.pm

#!/usr/bin/perl -w
package apc;
use Exporter;
@ISA = ('Exporter'); #???
@EXPORT = ('plancontroler');

sub plancontroler {
my $outdata=shift;

~maths stuff~

print " tc $totalcost pc $plancost ec $extracost pd $plandesc ac $acost we $extrac od $outdata wc $wcost pf $profit /n";
@rdata=("$totalcost","$plancost","$extracost","$plandesc","$acost","$extrac","$outdata","$wcost","$profit");

return @rdata;
}

1; #????

My problem is now reading @rdata back into the main program
and calling the different plan-files. I dont expect any difficulty there

But for the purpose of nutting it out I made one file called apc.pm I have 50 plan-files so I cant put 50 use statements at the top of the file. hmmm

I would be eternally grateful for a suggestion there.
Sorry mate this must be sausages and eggs to you but its like climbing Mt Everest to me I have only been at it a couple of months

Cheers Terry


1arryb
User

Mar 10, 2009, 8:42 AM

Post #6 of 24 (4241 views)
Re: [terry1738] subroutines [In reply to] Can't Post

Hi terry,

You could also pass a reference to @rdata in the plancontroler subroutine's parameter list:

Code
... 
sub plancontroler {
my $rdata_ref = shift;
...
@$rdata_ref = ("$totalcost","$plancost","$extracost","$plandesc","$acost","$extrac","$outdata","$wcost","$profit");
...


Then, in your main program:

Code
... 
my @rdata;
plancontroler(\@rdata);
...


For more on perl references, try 'perldoc perlref'.

Cheers,

Larry


KevinR
Veteran


Mar 10, 2009, 10:40 AM

Post #7 of 24 (4237 views)
Re: [terry1738] subroutines [In reply to] Can't Post

A bit of an aside, but you don't want to do this in perl:


Code
@rdata=("$totalcost","$plancost","$extracost","$plandesc","$acost","$extrac","$outdata","$wcost","$profit");


You don't want to put quotes around scalars like you have in the array. Quotes are for creating strings and can cause very hard to find bugs in perl programs when used improperly. In your program it will work fine but it is inefficient and as I said, has the potential to introduce bugs. Do it like this:


Code
@rdata=($totalcost, $plancost, $extracost, $plandesc, $acost, $extrac, $outdata , $wcost, $profit);


As far as the 50 plans go, you will probably have to write 50 different set of rules depending on the plan that is being calculated. A hash is good for that:


Code
my %plans = ( 
Plan1 => some rules you apply to plan1,
Plan2 => some rules you apply to plan2,
......
Plan50 => some rules you apply to plan50
);

-------------------------------------------------


terry1738
Novice

Mar 10, 2009, 6:30 PM

Post #8 of 24 (4231 views)
Re: [KevinR] subroutines [In reply to] Can't Post

Thanks Larry and Kevin

I really needed the direction you have given me as I think I was getting a confused with information overload.

I thought I had the answer to my 50 file problem this morning by doing this
$apc=xyz;
then
use $apc
but no go as use requires a bareword

I havent really got across hash's yet but I will figure it out.

Thanks again, very much appreciated
Cheers Terry


KevinR
Veteran


Mar 10, 2009, 6:40 PM

Post #9 of 24 (4230 views)
Re: [terry1738] subroutines [In reply to] Can't Post


In Reply To
Thanks Larry and Kevin

I really needed the direction you have given me as I think I was getting a confused with information overload.

I thought I had the answer to my 50 file problem this morning by doing this
$apc=xyz;
then
use $apc
but no go as use requires a bareword

I havent really got across hash's yet but I will figure it out.

Thanks again, very much appreciated
Cheers Terry


You can use "require" instead of "use".
-------------------------------------------------


FishMonger
Veteran / Moderator

Mar 11, 2009, 4:24 AM

Post #10 of 24 (4224 views)
Re: [KevinR] subroutines [In reply to] Can't Post

Loading 50 separate files via use or require is definitely the wrong approach.

You need no more than 3 files, 1 config file that holds the base info for the 50 plans, 1 module that uses that config file as its input and has routines that handle the calculations, and then finally the script that puts it all together.


terry1738
Novice

Mar 11, 2009, 7:17 AM

Post #11 of 24 (4218 views)
Re: [FishMonger] subroutines [In reply to] Can't Post

Hi

The strange thing I found with using require instead of use is that it would process the first client then come up with an error message however it did accept a variable as an argument by doing
eval "require $acp";
unfortunately only for 1 client

Thanks for your input, I did it that way when I first wrote the program years ago in bash.

However I found each plan needs to be in a separate file otherwise there is to much risk of corruption if something changes or you need to add another plan.

You also have much more freedom of control where a plan is still active but not current i.e. you don't sign up new clients to that plan.

Finally the way I have structured the corresponding data bases and support files would require another 2 months of work to change.

There are lots of other reasons as well.

What I really need is something like PHP include where I can pass the variables easily and the code in the subroutine behaves as if its part of the main program. Additionally its easy to call different subroutines with a variable.

I just didn't realise it would be this complicated in Perl when I planed it out. Although its probably very simple in reality I am just not far along the leaning curve to understand how to do it.

The problem I am facing is the same as leaning a language like english,
I know a few words like hello goodby and "where is the toilet" but to solve this little problem it looks like I have to know the whole blasted dictionary - backwards. Which I understand is my problem

Fortunately I have found a way around this issue and its working fine its just not elegant and I want it to work and look good as well.


KevinR
Veteran


Mar 11, 2009, 9:40 AM

Post #12 of 24 (4213 views)
Re: [terry1738] subroutines [In reply to] Can't Post

You should be able to do something like this:


Code
if ($plan eq 'Plan1') { 
require "plan1.pm";
}


That is of course over simplified but is the general idea.
-------------------------------------------------


terry1738
Novice

Mar 11, 2009, 4:07 PM

Post #13 of 24 (4207 views)
Re: [KevinR] subroutines [In reply to] Can't Post

If I replace use with require using a bareword it still only does 1 iteration so I am blowed if I know whats going on there.

I tried that idea but you need a block like this
if
elsif (1)
........
elsif (50)
and when I get this bit figured i plan to add my domain plans which is an additional 10 plans.


KevinR
Veteran


Mar 11, 2009, 6:04 PM

Post #14 of 24 (4204 views)
Re: [terry1738] subroutines [In reply to] Can't Post

Yes, if you go that route you need a block of if/elsif (or switch style statements). But 50 if/elsif's is not unusual in a script where there are many user options. Its been a while since I wrote or helped write a big program but I remember having 100+ blocks of conditionals for a script to determine what actions to take and which modules to load.
-------------------------------------------------


FishMonger
Veteran / Moderator

Mar 11, 2009, 6:13 PM

Post #15 of 24 (4204 views)
Re: [terry1738] subroutines [In reply to] Can't Post

Are you planning on loading all 50 plans or just 1 based on some criteria?

Can you show us a more complete sample of your code that handles the loading of the plan(s)?


FishMonger
Veteran / Moderator

Mar 11, 2009, 6:16 PM

Post #16 of 24 (4203 views)
Re: [terry1738] subroutines [In reply to] Can't Post

If you're planning on using a block of 50 if/else statements, you should use a dispatch table.


KevinR
Veteran


Mar 11, 2009, 7:08 PM

Post #17 of 24 (4200 views)
Re: [FishMonger] subroutines [In reply to] Can't Post


In Reply To
If you're planning on using a block of 50 if/else statements, you should use a dispatch table.


Good suggestion
-------------------------------------------------


terry1738
Novice

Mar 13, 2009, 12:38 AM

Post #18 of 24 (4191 views)
Re: [FishMonger] subroutines [In reply to] Can't Post

Hi

Here is the rough up (warts and all) of the main program that I am using to call these 50 sub routines , you asked me for.

At the moment I am only calling one subroutine in an attempt to get it to work and because of the problem I am facing trying to use a variable instead of a bare word in a "use" statement.

I will not be using 50 elsif statements as that would defeat the purpose of the design in that the clients data maintains the plan he is on.

So all I need to do with this program is get the client details from the data base and call the correct subroutine.

That seems to be possible using
eval "require $xxxx";
My problem is when I do

use apc;

it works but when do (without changing anything else)

require apc;

it fails with
Undefined subroutine &main::plancontroler called at ./adsldtb.pl line 89, <PHNUM> line 1.
So it seems they are not directly interchangeable

Thats my only issue now. If anyone can tell me why that is happening I think I can solve all the other issues.

Regards Terry

#!/usr/bin/perl -w
use Date::Manip;
use DBI;
use DBD::mysql;
#use apc;
require apc;
#$apc="apc";
#eval "require $apc";



my ($outdata, $extracost, $totalcost, $extrac, $wcost, $acost, $profit, $plancost);
# format: dbi:db type:db name:host:port
my $dsn = 'dbi:mysql:TAC:localhost:3306';

# set the user and password
my $user = 'root';
my $pass = 'xxxx';

# now connect and get a database handle
my $dbh = DBI->connect($dsn, $user, $pass) or die "Canít connect to the DB: $DBI::errstr\n";


#first get all the phone numbers I dont know if there is any value in getting this months numbers as we only need to get a value for last month
#because we are working out the costs for last month and adding the plan costs to overusage -
#numbers that appear in this months stuff should be different in that they are processed with the join date and the prorata is calculated

my $datethismonth = UnixDate(ParseDate("15th"),'%b');
my $datelastmonth = UnixDate(DateCalc(ParseDate("15th"), '-1m'),'%b');
my $infile = "/home/httpd/adslinfo/dft-data-upd.$datelastmonth";
my $found = 0;
open(PHNUM, $infile) || die "Error opening $infile : $!\n";
while(<PHNUM>) {
my $line = $_;
$found += 1;
@data = split(/,/, $line);
my $phone = $data[0];
my $dftcode = $data[1];
my $state = $data[2];
my $datefrom = $data[3];
my $dateto = $data[4];
my $status = $data[5];
my $indata = $data[6];
my $outdata = $data[7];
my $uptill = $data[8];
my $joined = $data[9];
my $plantxt = $data[10];
my $indatamb = int($indata / 1000000); #Do some maths
my $outdatamb = int($outdata / 1000000);


# print "$data[0] $data[1] $data[2] $data[3] $data[4] $data[5] $data[6] $data[7]$data[8]$data[9]$data[10] $indatamb $outdatamb \n";

#first prepare the query
my $sth = $dbh->prepare("select tac_plan, username, billing_email, date_format(udab_date,'%b'), udab_date
from _users where adsl_phone='$phone' and tac_plan like 'a%%'");
#then execute
$sth->execute;

@row = $sth->fetchrow_array();

my $username=$row[1];
my $udab=$row[4];
my $tplan=$row[0];
#if no user report
#print "$username \n";
#if exists delete orphan
if (!$username) {
#you will need to delete the file or the contents on each run because the numbers that existed in the file that dont exist now have been dealt with and secondly numbers would be appended on each run and that be bad

open(PORPHAN, ">>/usr/local/tacacts/tmp/orphan-adsl-numbers");
print PORPHAN "$phone \n";
close(PORPHAN);
}
#print "0 $row[0] 1 $row[1] 2 $row[2] 3 $row[3] 4 $row[4] 5 $row[5]\n";

#you need to nut this out udab is the last time this account was paid
#SO you can do MUCH MORE HERE like overdue and never paid come to mind also if its 0 or "" you should put in $joined and work out all the payments from then till now and send an email so we are aware there is an issue

if (!$row[4] || $row[4] eq "00-00-0000 00:00:00") {
my $sth1 = $dbh->prepare("update _users set udab_date='$joined' where username='terry'");
$sth1->execute;
open(BUDAB, ">>/tmp/budab");
print BUDAB "$phone, $joined \n";
close(BUDAB);
}

$fexist="/usr/local/tacacts/collect/active-plans/$row[0]";

#if it does not exist we REALLY want to know about it so do something here

if (-e $fexist) {
plancontroler("$outdatamb");
}
}


And this is the apc.pm subroutine I am calling


#!/usr/bin/perl -w
package apc; #:defines the name of the package (Like twig)
use Exporter; #use this package to make it work
@ISA = ('Exporter'); #I think this is some predefined array that you put this package into
@EXPORT = ('plancontroler'); #it exports the function below

sub plancontroler { #The function we want to export
my $outdata=shift;
#print "odg $outdata \n";

$plancost = 29.95;
if ( $outdata < 201 ) {
$extracost = 0;
}

if (($outdata > 200) && ($outdata < 267)) {
$extracost = (($outdata - 200)*.15);
}

if (($outdata > 266) && ($outdata < 501)) {
$extracost = 10;
}

if (($outdata > 500) && ($outdata < 1001)) {
$extracost = 20;
}

if ($outdata > 1000) {
$extracost = 30;
}

$totalcost = $plancost + $extracost;
$plandesc = "ADSL 256/64 Varible";

$acost = 28.60;
$extrac = ($outdata * .0055);
$wcost = ($acost + $extrac);
$profit = ($totalcost - $wcost);
my $rdata_ref = shift;
#@$rdata_ref= ($totalcost, $plancost, $extracost, $plandesc, $acost, $extrac, $outdata, $wcost, $profit);
}

1;


(This post was edited by terry1738 on Mar 13, 2009, 12:46 AM)


FishMonger
Veteran / Moderator

Mar 13, 2009, 7:20 AM

Post #19 of 24 (4186 views)
Re: [terry1738] subroutines [In reply to] Can't Post

Please use the code tags especially when posting blocks of code like you have in this post. The code tags help to separate your code from your comments and retains the formatting/indentation.


First, you need to realize that use and require are executed at different times; use statements happen at compile time (before any var assignments) and require statements happen at runtime. So, if you plan on using a var in the statement you need to use a require statement. Actually there is another option, but I don't want to add more confusion.

Another difference is that a use statement will import the plancontroler() subroutine, but the require statement won't, so you'll need to do an explicit import.


Code
my $module = 'apc.pm'; 
require $module;
apc->import;


You should not use lowercase names for your modules. By convention, lowercase names are reserved for pragmas (which are special compiler directive modules). Module names should be in Titlecase.

Instead of using the -w switch, it's better to use the warnings pragma and you should also be using the strict pragma in every script.

There's no need to explicitly load DBD::mysql. The DBI module loads the DBD:: module specified in the connect statement via a require statement.

It's better to use a lexical var for the filehandle and the 3 arg form of open.
So, this:

Code
open(PHNUM, $infile) || die "Error opening $infile : $!\n";  
while(<PHNUM>) {
my $line = $_;


Is better written as:

Code
open my $PHNUM, '<', $infile or die "Error opening $infile : $!"; 
while( my $line = <$PHNUM> ) {


Why are you using $found += 1; in the while loop, but fail to use that var latter in the script? If you really do need that counter, you should be using the built-in $. var which holds the current line number of the file you're looping over.

You should check the return code from every open call and take proper action if the open call fails.

Don't quote individual vars, see: 'perldoc -q quoting'
plancontroler("$outdatamb");

should be:
plancontroler($outdatamb);


FishMonger
Veteran / Moderator

Mar 13, 2009, 7:29 AM

Post #20 of 24 (4183 views)
Re: [terry1738] subroutines [In reply to] Can't Post

What are the differences and similarities between the plan modules? Can you post 1 or 2 others so I can see if we can refractor them and make things cleaner and more efficient?


(This post was edited by FishMonger on Mar 13, 2009, 7:30 AM)


terry1738
Novice

Mar 13, 2009, 11:55 PM

Post #21 of 24 (4177 views)
Re: [FishMonger] subroutines [In reply to] Can't Post

I cant thank you enough taking the time to have a look at my program.

Your explanations answer a lot of the things I could not figure out by myself.

I should have reread the Perldoc on require and tried a few examples before asking the question. But I doubt I would have twigged that one is compiled and the other is not for another few months.

I grabbed the code from my current program written in bash and worked my way through each module rewriting it in Perl and after a few rewrites it got a bit messy and that is why the $found += 1 is there - I forgot to remove it.

below are a couple of the plan subroutines with out the headers and footers for making them work as subroutines.


Code
my $plancost = 11.00; 

if ( $hours < 30 ) {
$extracost = 0;
}

if (($hours > 30) && ($hours < 60)) {
$extracost = 5.50;
}

if (($hours > 60) && ($hours < 120)) {
$extracost = 11;
}

if (($hours > 120) && ($hours < 200)){
$extracost = 16.50;
}

if ($hours > 200){
$extracost = 27.00;
}

my $totalcost = $plancost + $extracost;

#Profit
my $wsaler = tac;
my $acost = 7.50;
my $extrac = 0;
my $wcost = ($acost + $extrac);
my $profit = ($totalcost -($totalcost/11 + $wcost);


and


Code
$plancost = 69.95; 
if ($outdatamb < 501) {
$extracost = 0;
}
if (($outdatamb > 500) && ($outdatamb < 567)) {
$extracost = (($outdatamb - 500)*.15);
}
if (($outdatamb > 566) && ($outdatamb < 1501)) {
$extracost = 10;
}
if (($outdatamb > 1500) && ($outdatamb < 2501)) {
$extracost = 20;
}
if (($outdatamb > 2500) && ($outdatamb < 5001)) {
$extracost = 35;
}
if ($outdatamb > 5001) {
$extracost = 35;
}
$totalcost = $plancost + $extracost;
$plandesc = "ADSL 1500/256 Elite";

my $wsaler = dft;
my $acost = 48.78;
my $extrac = ($outdatamb * .0033);
my $wcost = ($acost + $extrac);
my $profit = ($totalcost - $wcost);


I have a number of simple ones like


Code
 
my $plancost = 124.95;
my $extracost = 0.00;

if ($outdatamb > 25000) {
$extracost = (($outdatamb - 25000)*.05);
}

my $totalcost = $plancost + $extracost;
my $plandesc = "ADSL 8000/384 Flat 25Gb for $124.95 then 5c/Mb";

my $wsaler = dft;
my $acost = 92.00;
my $extrac = ($outdatamb * .0044);
my $wcost = ($acost + $extrac);
my $profit = ($totalcost-($totalcost/11 + $wcost));



FishMonger
Veteran / Moderator

Mar 14, 2009, 6:39 AM

Post #22 of 24 (4174 views)
Re: [terry1738] subroutines [In reply to] Can't Post

First let me stress that loading/requiring all 50 plans is unnecessary. In fact, just having 50 plans like this seams IMO to be way over the top.

I was hoping the plans would have a recognizable pattern of increments that could be factored out, but you seam to be all over the map.

We could do a little work on cleaning up the code, but given the lack of consistency in the incrementation of the usage/cost, it will be difficult to factor out a proper formula.


terry1738
Novice

Mar 14, 2009, 7:11 AM

Post #23 of 24 (4171 views)
Re: [FishMonger] subroutines [In reply to] Can't Post

Thanks for the advice.

I f I only call 1 plan at a time as a subroutine does it matter if there are 50 or as many plans as I need as only 1 plan/subroutine is loaded on any iteration.

If its the wrong way to do it there is no problem loading them all into 1 file as you suggested previously however for reasons I stated I found that becomes unwieldy and error prone.

My ideal solution is to load them into a mysql data base as records but up to now I have not spent any time looking at this as it would seem a bit difficult given the diversity of the plan maths.

Again, I am grateful for your time I am sure I will be able to come up with a neater solution then I have currently now you have given me the answers to my issues.

Many thanks
Terry


KevinR
Veteran


Mar 23, 2009, 2:36 PM

Post #24 of 24 (4124 views)
Re: [terry1738] subroutines [In reply to] Can't Post

if you are using strict lines like this will no fly:

my $wsaler = dft;

only digits (and constants, but you're not using anyconstants) can be left unquoted, should be:

my $wsaler = 'dft';

and if you're not using strict, and your program is more than a few lines long, I hope you never make a typo.
-------------------------------------------------


(This post was edited by KevinR on Mar 23, 2009, 2:38 PM)

 
 


Search for (options) Powered by Gossamer Forum v.1.2.0

Web Applications & Managed Hosting Powered by Gossamer Threads
Visit our Mailing List Archives