File Coverage

lib/Bio/Graphics/Browser2.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package Bio::Graphics::Browser2;
2             # $Id$
3             # Globals and utilities for GBrowse and friends
4              
5             our $VERSION = '2.56';
6              
7 6     6   766144 use strict;
  6         10  
  6         137  
8 6     6   20 use warnings;
  6         7  
  6         146  
9 6     6   19 use base 'Bio::Graphics::Browser2::AuthorizedFeatureFile';
  6         8  
  6         2045  
10              
11             use File::Spec;
12             use File::Path 'mkpath';
13             use File::Basename 'dirname','basename';
14             use Text::ParseWords 'shellwords';
15             use File::Path 'mkpath';
16             use Bio::Graphics::Browser2::DataSource;
17             use Bio::Graphics::Browser2::Session;
18             use GBrowse::ConfigData;
19             use Carp qw(croak carp confess cluck);
20              
21             use constant DEFAULT_MASTER => 'GBrowse.conf';
22              
23             my (%CONFIG_CACHE,$HAS_DBFILE,$HAS_STORABLE);
24              
25             # Open a globals object with a config file in the standard location.
26             sub open_globals {
27             my $self = shift;
28             my $conf_dir = $self->config_base;
29             my $conf_file = $ENV{GBROWSE_MASTER} || DEFAULT_MASTER;
30             my $path = File::Spec->catfile($conf_dir,$conf_file);
31             die "No GBrowse configuration file at $path!" unless -r $path;
32             return $self->new($path);
33             }
34              
35             sub new {
36             my $class = shift;
37             my $config_file_path = shift;
38              
39             # Cache the config info so we don't need to reparse in a persistent (e.g. modperl) environment
40             my $mtime = (stat($config_file_path))[9] || 0;
41             if (exists $CONFIG_CACHE{$config_file_path}
42             && $CONFIG_CACHE{$config_file_path}{mtime} >= $mtime) {
43             return $CONFIG_CACHE{$config_file_path}{object};
44             }
45              
46             my $self = $class->SUPER::new(-file=>$config_file_path,
47             -safe=>1);
48              
49             # a little trick here -- force the setting of "config_base" from the config file
50             # base if not explicitly overridden
51             unless ($self->setting('general' => 'config_base')) {
52             my $dir = dirname($config_file_path);
53             $self->setting('general' => 'config_base',$dir);
54             }
55              
56             $CONFIG_CACHE{$config_file_path}{object} = $self;
57             $CONFIG_CACHE{$config_file_path}{mtime} = $mtime;
58             return $self;
59             }
60              
61             ## methods for dealing with paths
62             sub resolve_path {
63             my $self = shift;
64             my $path = shift;
65             my $path_type = shift; # one of "config" "htdocs" or "url"
66             return unless $path;
67             return $path if $path =~ m!^/!; # absolute path
68             return $path if $path =~ m!\|\s*$!; # a pipe
69             return $path if $path =~ m!^(http|ftp):!; # an URL
70             my $method = ${path_type}."_base";
71             $self->can($method) or confess "path_type must be one of 'config','htdocs', or 'url'";
72             my $base = $self->$method or return $path;
73             return File::Spec->catfile($base,$path);
74             }
75              
76             sub config_path {
77             my $self = shift;
78             my $option = shift;
79             $self->resolve_path($self->setting(general => $option),'config');
80             }
81              
82             sub htdocs_path {
83             my $self = shift;
84             my $option = shift;
85             $self->resolve_path($self->setting(general => $option),'htdocs')
86             || "$ENV{DOCUMENT_ROOT}/gbrowse2";
87             }
88              
89             sub url_path {
90             my $self = shift;
91             my $option = shift;
92             $self->resolve_path( scalar($self->setting(general => $option)),'url');
93             }
94              
95             sub config_base {$ENV{GBROWSE_CONF}
96             || eval {shift->setting(general=>'config_base')}
97             || GBrowse::ConfigData->config('conf')
98             || '/etc/GBrowse2' }
99             sub htdocs_base {$ENV{GBROWSE_HTDOCS}
100             || eval{shift->setting(general=>'htdocs_base')}
101             || GBrowse::ConfigData->config('htdocs')
102             || '/var/www/gbrowse2' }
103             sub url_base {eval{shift->setting(general=>'url_base')}
104             || basename(GBrowse::ConfigData->config('htdocs'))
105             || '/gbrowse2' }
106              
107             sub tmp_base {eval{shift->setting(general=>'tmp_base')}
108             || GBrowse::ConfigData->config('tmp')
109             || '/tmp' }
110             sub persistent_base {
111             my $self = shift;
112             my $base = $self->setting(general=>'persistent_base');
113             return $base || $self->tmp_base; # for compatibility with pre 2.27 installs
114             }
115             sub db_base {
116             my $self = shift;
117             my $base = $self->setting(general=>'db_base');
118             return $base || File::Spec->catfile(shift->persistent_base,'databases');
119             }
120             sub userdata_base {
121             my $self = shift;
122             my $base = $self->setting(general=>'userdata_base');
123             return $base || File::Spec->catfile($self->persistent_base,'userdata');
124             }
125              
126             # these are url-relative options
127             sub button_url { shift->url_path('buttons') }
128             sub balloon_url { shift->url_path('balloons') }
129             sub openid_url { shift->url_path('openid') }
130             sub js_url { shift->url_path('js') }
131             sub help_url { shift->url_path('gbrowse_help') }
132             sub stylesheet_url { shift->url_path('stylesheet') }
133             sub auth_plugin { shift->setting(general=>'authentication plugin') }
134              
135             # this returns the base URL and path info for use in constructing
136             # links. For example, if gbrowse is running at http://foo.bar/cgi-bin/gb2/gbrowse/yeast,
137             # it will return the list ('http://foo.bar/cgi-bin/gb2','yeast')
138             sub gbrowse_base {
139             my $self = shift;
140             my $url = CGI::url();
141             my $source = $self->get_source_from_cgi;
142             $source = CGI::escape($source);
143             $url =~ s!/[^/]*$!!;
144             return ($url,$source);
145             }
146              
147             # this returns the URL of the "master" gbrowse instance
148             sub gbrowse_url {
149             my $self = shift;
150             my $fallback_source = shift;
151             my ($base,$source) = $self->gbrowse_base;
152             $source ||= $fallback_source if $fallback_source;
153             return "$base/gbrowse/$source";
154             }
155              
156             sub make_path {
157             my $self = shift;
158             my $path = shift;
159             return unless $path =~ /^(.+)$/;
160             $path = $1;
161             mkpath($path,0,0777) unless -d $path;
162             }
163              
164             sub tmpdir {
165             my $self = shift;
166             my @components = @_;
167             my $path = File::Spec->catfile($self->tmp_base,@components);
168             $self->make_path($path) unless -d $path;
169             return $path;
170             }
171              
172             sub user_dir {
173             my $self = shift;
174             my @components = @_;
175             my $base = $self->userdata_base;
176             return File::Spec->catfile($base,@components);
177             }
178              
179             sub admin_dir {
180             my $self = shift;
181             my @components = @_;
182             my $path = $self->admin_dbs();
183             return File::Spec->catfile($path,@components);
184             }
185              
186             sub tmpimage_dir {
187             my $self = shift;
188             return $self->tmpdir('images',@_);
189             }
190              
191             sub image_url {
192             my $self = shift;
193             my $path = File::Spec->catfile($self->url_base,'i');
194             return $path;
195             }
196              
197             sub cache_dir {
198             my $self = shift;
199             my $path = File::Spec->catfile($self->tmp_base,'cache',@_);
200             $self->make_path($path) unless -d $path;
201             return $path;
202             }
203              
204             sub session_locks {
205             my $self = shift;
206             my $path = File::Spec->catfile($self->tmp_base,'locks',@_);
207             $self->make_path($path) unless -d $path;
208             return $path;
209             }
210              
211             # return one of
212             # 'flock' -- standard flock locking
213             # 'nfs' -- use File::NFSLock
214             # 'mysql' -- use mysql advisory locks
215             sub session_locktype {
216             my $self = shift;
217             return $self->setting(general=>'session lock type') || 'default';
218             }
219              
220             sub session_dir {
221             my $self = shift;
222             my $path = File::Spec->catfile($self->persistent_base,'sessions',@_);
223             $self->make_path($path) unless -d $path;
224             return $path;
225             }
226              
227             sub slave_dir {
228             my $self = shift;
229             my $path = $self->setting(general=>'tmp_slave') || '/tmp/gbrowse_slave';
230             $self->make_path($path) unless -d $path;
231             return $path;
232             }
233              
234             sub slave_status_path {
235             my $self = shift;
236             my $path = File::Spec->catfile($self->tmp_base,'slave_status');
237             return $path;
238             }
239              
240             # these are relative to the config base
241             sub plugin_path { shift->config_path('plugin_path') }
242             sub language_path { shift->config_path('language_path') }
243             sub templates_path { shift->config_path('templates_path') }
244             sub moby_path { shift->config_path('moby_path') }
245              
246             sub preload_datasources { shift->setting(general=>'preload data sources') || 0 } # default not-preload
247             sub global_timeout { shift->setting(general=>'global_timeout') || 60 }
248             sub remember_settings_time { shift->setting(general=>'expire session') || '1M' }
249             sub cache_time { shift->setting(general=>'expire cache') || '2h' }
250             sub upload_time { shift->setting(general=>'expire uploads') || '6w' }
251             sub datasources_expire { shift->setting(general=>'expire data sources') || '10m' }
252             sub url_fetch_timeout { shift->setting(general=>'url_fetch_timeout') }
253             sub url_fetch_max_size { shift->setting(general=>'url_fetch_max_size') }
254              
255             sub application_name { shift->setting(general=>'application_name') || 'GBrowse' }
256             sub application_name_long { shift->setting(general=>'application_name_long') || 'The Generic Genome Browser' }
257             sub email_address { shift->setting(general=>'email_address') || 'noreply@gbrowse.com' }
258             sub smtp { my $smtp = shift->setting(general=>'smtp_gateway'); return if $smtp eq 'none'; return $smtp }
259             sub smtp_enabled { return defined shift->smtp; }
260             sub user_account_db { shift->setting(general=>'user_account_db') } # Used by uploads & user databases, they set their own defaults.
261             sub user_accounts { my $self = shift;
262             return $self->setting(general=>'user_accounts') ||
263             $self->setting(general=>'user_accounts') ||
264             0; }
265             sub user_accounts_allow_registration
266             {
267             my $val = shift->setting(general=>'user_accounts_registration');
268             return 1 unless defined $val;
269             return $val;
270             }
271             sub user_accounts_allow_openid
272             {
273             my $val = shift->setting(general=>'user_accounts_openid');
274             return 1 unless defined $val;
275             return $val;
276             }
277             sub public_files { shift->setting(general=>'public_files') || 10 }
278             sub admin_account { shift->setting(general=>'admin_account') }
279             sub admin_dbs { shift->setting(general=>'admin_dbs') }
280             sub openid_secret {
281             return GBrowse::ConfigData->config('OpenIDConsumerSecret')
282             }
283              
284             # uploads
285             sub upload_db_adaptor {
286             my $self = shift;
287             my $adaptor = $self->setting(general=>'upload_db_adaptor') || $self->setting(general=>'userdb_adaptor');
288             $adaptor or return;
289             warn "The upload_db_adaptor in your Gbrowse.conf file isn't in the DBI:: format: remember, it's not a connection string."
290             if $adaptor =~ /^DBI/ && $adaptor !~ /(^DBI::+)/i;
291             return $adaptor;
292             }
293             sub upload_db_host {
294             my $self = shift;
295             return $self->setting(general=>'upload_db_host') || $self->setting(general=>'userdb_host') || 'localhost'
296             }
297             sub upload_db_user {
298             my $self = shift;
299             return $self->setting(general=>'upload_db_user') || $self->setting(general=>'userdb_user') || '';
300             }
301             sub upload_db_pass {
302             my $self = shift;
303             return $self->setting(general=>'upload_db_pass') || $self->setting(general=>'userdb_pass') || '';
304             }
305              
306             sub session_driver {
307             my $self = shift;
308             my $driver = $self->setting(general=>'session driver');
309             return $driver if $driver;
310              
311             $HAS_DBFILE = eval "require DB_File; 1" || 0
312             unless defined $HAS_DBFILE;
313             $HAS_STORABLE = eval "require Storable; 1" || 0
314             unless defined $HAS_STORABLE;
315              
316             my $sdriver = $HAS_DBFILE ? 'db_file' : 'file';
317             my $serializer = $HAS_STORABLE ? 'storable' : 'default';
318              
319             return "driver:$sdriver;serializer:$serializer";
320             }
321              
322             sub session_args {
323             my $self = shift;
324             my %args = shellwords($self->setting(general=>'session args')||'');
325             return \%args if %args;
326             return {Directory=>$self->session_dir};
327             }
328              
329             ## methods for dealing with data sources
330             sub data_sources {
331             return sort grep {!/^\s*=~/ && !/:plugin$/} shift->SUPER::configured_types();
332             }
333              
334             sub data_source_description {
335             my $self = shift;
336             my $dsn = shift;
337             return $self->setting($dsn=>'description');
338             }
339              
340             sub data_source_restrict {
341             my $self = shift;
342             my $dsn = shift;
343             return $self->setting($dsn=>'restrict');
344             }
345              
346             sub data_source_show {
347             my $self = shift;
348             my $dsn = shift;
349             my ($username,$authenticator) = @_;
350             return if $self->setting($dsn=>'hide');
351              
352             # because globals are cached between use, we do not want usernames
353             # to be defined outside the scope of this call
354             local $self->{'.authenticated_username'} = $username if defined $username;
355             local $self->{'.authenticator'} = $authenticator if defined $authenticator;
356             return $self->authorized($dsn);
357             }
358              
359             sub data_source_path {
360             my $self = shift;
361             my $dsn = shift;
362             my ($regex_key) = grep { $dsn =~ /^$_$/ } map { $_ =~ s/^=~//; $_ } grep { $_ =~ /^=~/ } keys(%{$self->{config}});
363             if ($regex_key) {
364             my $path = $self->resolve_path($self->setting("=~".$regex_key=>'path'),'config');
365             my @matches = ($dsn =~ /$regex_key/);
366             for (my $i = 1; $i <= scalar(@matches); $i++) {
367             $path =~ s/\$$i/$matches[$i-1]/g;
368             }
369             return $self->resolve_path($path, 'config');
370             }
371             my $path = $self->setting($dsn=>'path') or return;
372             $self->resolve_path($path,'config');
373             }
374              
375             sub authorized {
376             my $self = shift;
377             my $sourcename = shift;
378             my ($username,$authenticator) = @_;
379             local $self->{'.authenticated_username'} = $username if defined $username;
380             local $self->{'.authenticator'} = $authenticator if defined $authenticator;
381             return $self->SUPER::authorized($sourcename);
382             }
383              
384             sub create_data_source {
385             my $self = shift;
386             my $dsn = shift;
387             my $path = $self->data_source_path($dsn) or return;
388             my ($regex_key) = grep { $dsn =~ /^$_$/ } map { $_ =~ s/^=~//; $_ } grep { $_ =~ /^=~/ } keys(%{$self->{config}});
389             my $name = $dsn;
390             if ($regex_key) { $dsn = "=~".$regex_key; }
391             my $source = Bio::Graphics::Browser2::DataSource->new($path,
392             $name,
393             $self->data_source_description($dsn),
394             $self) or return;
395             if (my $adbs = $self->admin_dbs) {
396             my $path = File::Spec->catfile($adbs,$dsn);
397             my $expr = "$path/*/*.conf";
398             $source->add_conf_files($expr);
399             }
400             return $source;
401             }
402              
403             sub max_features {
404             my $self = shift;
405             my $max = $self->setting(general => 'maximum features');
406             return 5000 unless defined $max;
407             return $max;
408             }
409              
410             sub default_source {
411             my $self = shift;
412             my $source = $self->setting(general => 'default source');
413             return $source if $self->valid_source($source);
414             return ($self->data_sources)[0];
415             }
416              
417             sub valid_source {
418             my $self = shift;
419             my $proposed_source = shift;
420              
421             if (!exists($self->{config}{$proposed_source})) {
422             my ($regex_key) = grep { $proposed_source =~ /^$_$/ } map { $_ =~ s/^=~//; $_ } grep { $_ =~ /^=~/ } keys(%{$self->{config}});
423             return unless $regex_key;
424             my $path = $self->data_source_path("=~" . $regex_key) or return;
425             return -e $path || $path =~ /\|\s*$/;
426             }
427              
428             return unless exists $self->{config}{$proposed_source};
429             my $path = $self->data_source_path($proposed_source) or return;
430             return -e $path || $path =~ /\|\s*$/;
431             }
432              
433             sub get_source_from_cgi {
434             my $self = shift;
435              
436             my $source = CGI::param('source') || CGI::param('src') || CGI::path_info();
437             $source =~ s!\#$!!; # get rid of trailing # left by IE
438             $source =~ s!^/+!!; # get rid of leading & trailing / from path_info()
439             $source =~ s!/+$!!;
440              
441             $source;
442             }
443              
444             sub update_data_source {
445             my $self = shift;
446             my $session = shift;
447             my $new_source = shift;
448             my $old_source = $session->source || $self->default_source;
449              
450             $new_source ||= $self->get_source_from_cgi();
451              
452             my $source;
453             if ($self->valid_source($new_source)) {
454             $session->source($new_source);
455             $source = $new_source;
456             } else {
457             my $fallback_source = $self->valid_source($old_source)
458             ? $old_source
459             : $self->default_source;
460             $session->source($fallback_source);
461             $source = $fallback_source;
462             }
463             return $source;
464             }
465              
466             sub time2sec {
467             my $self = shift;
468             my $time = shift;
469             $time =~ s/\s*#.*$//; # strip comments
470              
471             my(%mult) = ('s'=>1,
472             'm'=>60,
473             'h'=>60*60,
474             'd'=>60*60*24,
475             'w'=>60*60*24*7,
476             'M'=>60*60*24*30,
477             'y'=>60*60*24*365);
478             my $offset = $time;
479             if (!$time || (lc($time) eq 'now')) {
480             $offset = 0;
481             } elsif ($time=~/^([+-]?(?:\d+|\d*\.\d*))([smhdwMy])/) {
482             $offset = ($mult{$2} || 1)*$1;
483             }
484             return $offset;
485             }
486              
487             ## methods for dealing with the session
488             sub session {
489             my $self = shift;
490             my ($id,$mode) = @_;
491              
492             $id ||= undef;
493             my @args = (driver => $self->session_driver,
494             args => $self->session_args,
495             source => $self->default_source,
496             lockdir => $self->session_locks,
497             locktype => $self->session_locktype,
498             mode => $mode || 'exclusive',
499             expires => $self->remember_settings_time);
500             return Bio::Graphics::Browser2::Session->new(@args,id => $id);
501             }
502              
503             sub authorized_session {
504             my $self = shift;
505             my ($id,$authority,$shared_ok) = @_;
506              
507             $id ||= undef;
508             my $session = $self->session($id,$shared_ok ? 'shared' : 'exclusive');
509              
510             return $session unless $session->private;
511              
512             if ($session->match_nonce($authority,CGI::remote_addr())) {
513             return $session;
514             } else {
515             cluck "UNAUTHORIZED ATTEMPT";
516             return $self->session('xyzzy');
517             }
518             }
519              
520             1;