Skip to content
  • 12 commits
  • 45 files changed
  • 0 commit comments
  • 2 contributors
Commits on Aug 05, 2011
@jonjensen jonjensen Improve camp_subdirectories handling to allow deeper paths dc66697
@jonjensen jonjensen Add better support for db_host option for PostgreSQL
This is especially helpful on Debian.
0181206
@jonjensen jonjensen Incompatible change: No longer interpolate httpd/conf/httpd.conf by d…
…efault

After applying this update, you must add httpd/conf/httpd.conf to
camp-config-files or your camp Apache configuration will no longer be
interpolated and copied into place.

This is necessary to enable camps to work right with nginx, and no longer
assume Apache is used at all.
e55231a
@jonjensen jonjensen Add support for other application servers with app_* settings 5b41c0d
@jonjensen jonjensen Support non-Apache webservers with skip_apache and httpd_{start,stop,…
…restart} settings
38439c1
@jonjensen jonjensen Quell undefined variable warnings when app_* settings are not used 4686fff
@jonjensen jonjensen Increase version to 3.02
This coincides with introduction of support for other web and app servers.
3cf48e5
Commits on Aug 12, 2011
@jonjensen jonjensen Note GPL 3+ license on website 8cf55c3
Commits on Aug 17, 2011
@jonjensen jonjensen Add YAPC::EU 2011 presentation on camps 312b6b3
Commits on Sep 09, 2011
@jonjensen jonjensen Make `camp-info -a` show camps in a sane deterministic order f9711f3
Commits on Sep 20, 2011
@jonjensen jonjensen Fix mkcamp --skipvcs which wasn't creating camp base directory 545df61
Commits on Sep 30, 2011
@rmtemplet Fixed assumption in database role password replacement
This patch fixes an issue where the password caching system for
the databases assumes the camp being created or updated uses
the same database as the main camp database.  I adjusted this
to pull the db_type from the configuration for the camp
being made.
89686fe
Showing with 4,245 additions and 53 deletions.
  1. +1 −1 README
  2. +1 −1 bin/camp-info
  3. +1 −1 bin/camp-lint
  4. +11 −3 bin/mkcamp
  5. +1 −1 bin/mkcampsymlinks
  6. +1 −1 bin/mysql_camp
  7. +3 −2 bin/psql_camp
  8. +1 −1 bin/re
  9. +1 −1 bin/re-all-camps
  10. +4 −1 bin/refresh-camp
  11. +1 −1 bin/rmcamp
  12. +1 −1 bin/with-camp
  13. +2 −2 lib/Camp/Config.pm
  14. +105 −34 lib/Camp/Master.pm
  15. BIN website/camps-presentation-20110817/citypass-camps-index.png
  16. BIN website/camps-presentation-20110817/continuous-delivery-book-cover.jpg
  17. BIN website/camps-presentation-20110817/devcamps.org-home-smaller.png
  18. +562 −0 website/camps-presentation-20110817/index.html
  19. BIN website/camps-presentation-20110817/pad-small.png
  20. +1 −0 website/camps-presentation-20110817/page.css
  21. BIN website/camps-presentation-20110817/ui/advanced_utf/backgrnd.png
  22. BIN website/camps-presentation-20110817/ui/advanced_utf/footer.jpg
  23. +22 −0 website/camps-presentation-20110817/ui/advanced_utf/framing.css
  24. BIN website/camps-presentation-20110817/ui/advanced_utf/header.png
  25. +7 −0 website/camps-presentation-20110817/ui/advanced_utf/opera.css
  26. +13 −0 website/camps-presentation-20110817/ui/advanced_utf/outline.css
  27. BIN website/camps-presentation-20110817/ui/advanced_utf/pattern.png
  28. +94 −0 website/camps-presentation-20110817/ui/advanced_utf/pretty.css
  29. +3 −0 website/camps-presentation-20110817/ui/advanced_utf/print.css
  30. +8 −0 website/camps-presentation-20110817/ui/advanced_utf/s5-core.css
  31. +3 −0 website/camps-presentation-20110817/ui/advanced_utf/slides.css
  32. +2,665 −0 website/camps-presentation-20110817/ui/advanced_utf/slides.js
  33. +29 −0 website/camps-presentation-20110817/ui/audio_support/license.txt
  34. BIN website/camps-presentation-20110817/ui/audio_support/null.mp3
  35. +658 −0 website/camps-presentation-20110817/ui/audio_support/soundmanager2.js
  36. BIN website/camps-presentation-20110817/ui/audio_support/soundmanager2.swf
  37. BIN website/camps-presentation-20110817/ui/graphic_support/blank.gif
  38. BIN website/camps-presentation-20110817/ui/graphic_support/finish.gif
  39. +1 −0 website/camps-presentation-20110817/ui/graphic_support/fixed.js
  40. BIN website/camps-presentation-20110817/ui/graphic_support/help.jpg
  41. +42 −0 website/camps-presentation-20110817/ui/graphic_support/iepngfix.htc
  42. BIN website/camps-presentation-20110817/ui/graphic_support/numeric.png
  43. BIN website/camps-presentation-20110817/ui/graphic_support/progress.gif
  44. +1 −1 website/code.php
  45. +2 −1 website/documentation.php
View
2 README
@@ -1,7 +1,7 @@
Development camps: better development environments
http://www.devcamps.org/
-Version 3.01
+Version 3.02
Copyright (C) 2006-2011 End Point Corporation
Released under GPLv3+
View
2 bin/camp-info
@@ -153,7 +153,7 @@ camp-info - camp information getter/setter
=head1 VERSION
-3.00
+3.02
=head1 AUTHOR
View
2 bin/camp-lint
@@ -140,7 +140,7 @@ This checks for:
=head1 VERSION
-3.00
+3.02
=head1 AUTHOR
View
14 bin/mkcamp
@@ -102,12 +102,20 @@ print "Configuration hash:\n", Dumper($conf);
# Add to camp database
register_camp($opt{comment}) unless $opt{skipcamp};
-# Check out bulk of files from version control repository
-vcs_checkout($opt{replace}) unless $opt{skipvcs};
+# Make camp base directory and/or check out files from version control repository
+if ($opt{skipvcs}) {
+ create_camp_path($conf->{number}, $opt{replace});
+}
+else {
+ vcs_checkout($opt{replace});
+}
# Must prepare database before installing templates; do so now.
prepare_database($opt{replace}) unless $opt{skipdb};
+# Create needed empty directories
+create_camp_subdirectories();
+
# Perform simple copy/symlink operations
process_copy_paths();
@@ -155,7 +163,7 @@ mkcamp - create a new camp
=head1 VERSION
-3.00
+3.03
=head1 SYNOPSIS
View
2 bin/mkcampsymlinks
@@ -32,7 +32,7 @@ mkcampsymlinks - create symlinks with camp information
=head1 VERSION
-3.00
+3.02
=head1 AUTHOR
View
2 bin/mysql_camp
@@ -72,7 +72,7 @@ mysql_camp - invoke the mysql client for a camp MySQL instance
=head1 VERSION
-3.00
+3.02
=head1 SYNOPSIS
View
5 bin/psql_camp
@@ -29,12 +29,13 @@ initialize(
);
my $conf = config_hash();
+my $host = $conf->{db_host};
my $port = $conf->{db_port};
$ENV{PGUSER} ||= $conf->{db_default_user};
$ENV{PGDATABASE} ||= $conf->{db_default_database};
-exec '/usr/bin/psql', '-p', $port, @ARGV;
+exec '/usr/bin/psql', '-h', $host, '-p', $port, @ARGV;
=pod
@@ -45,7 +46,7 @@ psql_camp - invoke the psql client for a camp PostgreSQL instance
=head1 VERSION
-3.00
+3.02
=head1 SYNOPSIS
View
2 bin/re
@@ -112,7 +112,7 @@ re - restart/start/stop one or more camp services
=head1 VERSION
-3.00
+3.02
=head1 AUTHOR
View
2 bin/re-all-camps
@@ -71,7 +71,7 @@ re-all-camps - start/stop/restart all camps
=head1 VERSION
-3.00
+3.02
=head1 DESCRIPTION
View
5 bin/refresh-camp
@@ -65,8 +65,11 @@ if (!$showpod) {
}
if ($opt{files}) {
+ create_camp_subdirectories();
+
# process_copy_paths() uses rsync for file copying, so it functions like an update
process_copy_paths();
+
$operation++;
}
@@ -96,7 +99,7 @@ refresh-camp - refresh various components within a camp
=head1 VERSION
-3.00
+3.02
=head1 SYNOPSIS
View
2 bin/rmcamp
@@ -45,7 +45,7 @@ rmcamp - remove a camp
=head1 VERSION
-3.00
+3.02
=head1 SYNOPSIS
View
2 bin/with-camp
@@ -57,7 +57,7 @@ with-camp - wrapper to run another command with camp awareness
=head1 VERSION
-3.00
+3.02
=head1 DESCRIPTION
View
4 lib/Camp/Config.pm
@@ -9,7 +9,7 @@ use DBI;
use Safe;
use Scalar::Util qw/blessed/;
-our $VERSION = '3.00';
+our $VERSION = '3.02';
my %package_singletons;
my @setting_names = qw(
@@ -711,7 +711,7 @@ Camp::Config - module for determining operating environment and configuration se
=head1 VERSION
-3.00
+3.02
=head1 DESCRIPTION
View
139 lib/Camp/Master.pm
@@ -16,7 +16,7 @@ use DBI;
use Exporter;
use base qw(Exporter);
-our $VERSION = '3.01';
+our $VERSION = '3.02';
@Camp::Master::EXPORT = qw(
base_path
@@ -32,6 +32,7 @@ our $VERSION = '3.01';
camp_user_tmpdir
config_hash
create_camp_path
+ create_camp_subdirectories
dbh
db_path
default_camp_type
@@ -40,6 +41,7 @@ our $VERSION = '3.01';
get_next_camp_number
has_ic
has_rails
+ has_app
initialize
install_templates
mysql_path
@@ -78,7 +80,7 @@ Camp::Master - library routines for management of camps
=head1 VERSION
-3.01
+3.02
=cut
@@ -87,6 +89,7 @@ my (
@base_edits,
@edits,
%edits,
+ $has_app,
$has_rails,
$has_ic,
$initialized,
@@ -104,7 +107,6 @@ my (
);
@base_edits = qw(
- httpd/conf/httpd.conf
);
$base_camp_user = 'camp';
@@ -300,6 +302,19 @@ sub type_path {
return File::Spec->catfile( base_path(), $local_type, );
}
+sub create_camp_subdirectories {
+ my $conf = config_hash();
+
+ my $dirs = $conf->{camp_subdirectories};
+ ref($dirs) eq 'ARRAY' and @$dirs or return 1;
+
+ -d $conf->{path} or die "Error in camp_subdirectories: main camp path '$conf->{path}' doesn't exist\n";
+
+ my $cwd = pushd($conf->{path}) or die "Couldn't chdir $conf->{path}: $!\n";
+
+ return File::Path::mkpath(@$dirs, { verbose => 1 });
+}
+
sub copy_paths_config_path {
return File::Spec->catfile( type_path(), 'copy-paths.yml' );
}
@@ -572,6 +587,13 @@ sub db_config_path_mysql {
die "Cannot locate mysql/my.cnf in type definition or base camp user!\n";
}
+sub has_app {
+ die "Cannot call has_app() until package has been initialized!\n" unless $initialized;
+ my $conf = config_hash();
+ return $has_app if defined $has_app;
+ return $has_app = (grep { defined and /\S/ } map { $conf->{"app_$_"} } qw( start stop )) ? 1 : 0;
+}
+
sub has_rails {
die "Cannot call has_rails() until package has been initialized!\n" unless $initialized;
return $has_rails if defined $has_rails;
@@ -654,7 +676,7 @@ sub get_camp_info {
sub get_all_camp_info {
my $username = shift;
my $dbh = dbh();
- my $sth = $dbh->prepare('SELECT camp_number,camp_type,create_date,comment FROM camps WHERE username = ?');
+ my $sth = $dbh->prepare('SELECT camp_number,camp_type,create_date,comment FROM camps WHERE username = ? ORDER BY camp_number');
my $rc = $sth->execute($username);
my $out = "Camp #\tCamp Type\tCamp Creation Date\t\tComment\n";
while ( my $row = $sth->fetchrow_hashref() ) {
@@ -701,7 +723,8 @@ sub camp_user_tmpdir {
sub _camp_db_type_dispatcher {
my $name = shift;
- my $type = camp_db_type();
+ my $config = config_hash();
+ my $type = $config->{db_type};
my $sub = __PACKAGE__->can( "${name}_$type" );
die "No function $name for database type $type!\n" unless $sub;
return $sub;
@@ -926,6 +949,26 @@ Specify location of configuration file under "httpd_path" and include that in co
For example, "conf/apache2.conf", used when executable has been compiled with a specific full path. To detect need for this option see httpd -V output and check for "SERVER_CONFIG_FILE" with a value similar to "/etc/apache2/apache2.conf" (any path beginning with "/").
+=item skip_apache
+
+A boolean (0/1) setting that determines whether to skip Apache-specific httpd server setup, for example when using nginx.
+
+=item httpd_start
+
+When setting skip_apache, set this to the command to start your webserver. An example with nginx:
+
+ /usr/sbin/nginx -c __CAMP_PATH__/nginx/nginx.conf
+
+=item httpd_stop
+
+When setting skip_apache, set this to the command to stop your webserver. An example with nginx:
+
+ pid=`cat __CAMP_PATH__/var/run/nginx.pid 2>/dev/null` && kill $pid
+
+=item httpd_restart
+
+pid=`cat __CAMP_PATH__/var/run/nginx.pid 2>/dev/null` && kill -HUP $pid || /usr/sbin/nginx -c __CAMP_PATH__/nginx/nginx.conf
+
=item skip_ssl_cert_gen
A boolean (0/1) setting that determines whether to skip generation of a self-signed SSL certificate for the HTTP server.
@@ -967,6 +1010,24 @@ directly within a virtualhost's <Proxy balancer://...>...</Proxy> container. De
to using three mongrel listeners with ports incremented by one starting at the
mongrel_base_port.
+=item app_start
+
+When using an application server other than those natively supported in camps, set this to the command to start your application server. For example (using a custom startup script):
+
+ __CAMP_PATH__/bin/start-unicorn
+
+=item app_stop
+
+Command to stop your application server. An example for Ruby's Unicorn:
+
+ pid=`cat __CAMP_PATH__/var/run/unicorn.pid 2>/dev/null` && kill $pid
+
+=item app_restart
+
+Command to restart your application server. An example for Ruby's Unicorn:
+
+ pid=`cat __CAMP_PATH__/var/run/unicorn.pid 2>/dev/null` && kill -HUP $pid || __CAMP_PATH__/bin/start-unicorn
+
=item db_type
The type of database server used for the camp; will be either 'pg' for Postgres or 'mysql'
@@ -1828,12 +1889,15 @@ sub _ssl_private_key {
sub prepare_apache {
my $conf = config_hash();
- # create empty directories
- mkpath([ map { File::Spec->catfile( $conf->{httpd_path}, $_, ) } qw( conf logs run ) ]);
- # symlink to system-wide Apache modules
- symlink $conf->{httpd_lib_path}, File::Spec->catfile($conf->{httpd_path}, 'modules')
- or die "Couldn't symlink Apache modules directory\n";
+ unless ($conf->{skip_apache}) {
+ # create empty directories
+ mkpath([ map { File::Spec->catfile( $conf->{httpd_path}, $_, ) } qw( conf logs run ) ]);
+
+ # symlink to system-wide Apache modules
+ symlink $conf->{httpd_lib_path}, File::Spec->catfile($conf->{httpd_path}, 'modules')
+ or die "Couldn't symlink Apache modules directory\n";
+ }
# Create SSL certificate
unless ($conf->{skip_ssl_cert_gen}) {
@@ -1896,13 +1960,6 @@ when copied into the new camp itself. Thus, a file at /home/camp/some_type/etc/
would be registered in B<camp-config-files> with a path relative to etc/, or "blah/foo.conf",
and would be installed at /home/some_user/campNN/blah/foo.conf after parsing.
-One file is always parsed by default, regardless of specification in the config file:
-
- httpd/conf/httpd.conf
-
-This is the base Apache configuration, and Apache is thus always expected to live at httpd
-within a camp. You could theoretically change this in your camp, but do so at your peril.
-
Also, some assumptions are made about what files will be included if your camp uses
Interchange versus Rails:
@@ -2444,7 +2501,7 @@ sub db_connect {
sub _db_connect_pg {
my %opt = @_;
my $conf = config_hash();
- my $cmd = "psql -X -p $conf->{db_port} -U $opt{user} -d $opt{database}";
+ my $cmd = "psql -X -h $conf->{db_host} -p $conf->{db_port} -U $opt{user} -d $opt{database}";
print "Connecting to Postgres: $cmd\n";
open my $PSQL, "| $cmd"
or die "Error opening pipe to psql: $!\n";
@@ -2546,7 +2603,7 @@ sub _import_db_cmd_pg {
# But for newer ones, use that instead of template1 so we don't pollute the
# template db if something goes wrong in the import.
my $dbname = (db_version_pg() < 8.0) ? 'template1' : 'postgres';
- return "psql -X -p $conf->{db_port} -U postgres -d $dbname -f $script";
+ return "psql -X -h $conf->{db_host} -p $conf->{db_port} -U postgres -d $dbname -f $script";
}
sub _import_db_cmd_mysql {
@@ -2663,8 +2720,9 @@ sub server_control {
);
my $dbtype = camp_db_type();
- $services{ic} = \&ic_control if has_ic();
+ $services{ic} = \&ic_control if has_ic();
$services{rails} = \&rails_control if has_rails();
+ $services{app} = \&app_control if has_app();
$services{$dbtype} = $db_services{$dbtype};
delete $services{db} if $conf_hash->{skip_db};
@@ -2675,6 +2733,7 @@ sub server_control {
db
ic
rails
+ app
);
die "Invalid action '$action' specified!\n" unless $actions{$action};
@@ -2701,6 +2760,15 @@ sub server_control {
return scalar(@services_to_start);
}
+sub app_control {
+ my $action = shift;
+ my $conf = config_hash();
+
+ my $cmd = $conf->{"app_$action"};
+ $cmd and do_system_soft($cmd) == 0 and return 1;
+ return;
+}
+
sub rails_control {
my $action = shift;
my $conf = config_hash();
@@ -2767,7 +2835,7 @@ sub _db_control_pg {
die "Need db_data definition!\n"
unless defined $conf->{db_data}
and $conf->{db_data} =~ /\S/;
- my $cmd = "pg_ctl -D $conf->{db_data} -l $conf->{db_tmpdir}/pgstartup.log -m fast";
+ my $cmd = "PGHOST=$conf->{db_host} pg_ctl -D $conf->{db_data} -l $conf->{db_tmpdir}/pgstartup.log -m fast";
# Work around old pg_ctl's terrible -w implementation, as evidenced by
# this comment there: "FIXME: This is horribly misconceived."
my $manual_wait = (db_version_pg() < 8.0);
@@ -2782,22 +2850,25 @@ sub _db_control_pg {
sub httpd_control {
my $action = shift;
my $conf = config_hash();
- die "Need httpd_cmd_path definition!\n"
- unless defined $conf->{httpd_cmd_path}
- and $conf->{httpd_cmd_path} =~ /\S/;
- die "Need httpd_path definition!\n"
- unless defined $conf->{httpd_path}
- and $conf->{httpd_path} =~ /\S/;
- my $cmd = "$conf->{httpd_cmd_path} -d $conf->{httpd_path} -k $action";
- $cmd .= join('', map { " -D$_" } grep /\S/, split / /, $conf->{httpd_define})
- if $conf->{httpd_define};
- if (defined $conf->{httpd_specify_conf} and $conf->{httpd_specify_conf} =~ /\S/) {
- $cmd .= " -f $conf->{httpd_path}/$conf->{httpd_specify_conf}";
+ my $cmd = $conf->{"httpd_$action"};
+ if (!$conf->{skip_apache} and !$cmd) {
+ die "Need httpd_cmd_path definition!\n"
+ unless defined $conf->{httpd_cmd_path}
+ and $conf->{httpd_cmd_path} =~ /\S/;
+ die "Need httpd_path definition!\n"
+ unless defined $conf->{httpd_path}
+ and $conf->{httpd_path} =~ /\S/;
+
+ $cmd = "$conf->{httpd_cmd_path} -d $conf->{httpd_path} -k $action";
+ $cmd .= join('', map { " -D$_" } grep /\S/, split / /, $conf->{httpd_define})
+ if $conf->{httpd_define};
+ if (defined $conf->{httpd_specify_conf} and $conf->{httpd_specify_conf} =~ /\S/) {
+ $cmd .= " -f $conf->{httpd_path}/$conf->{httpd_specify_conf}";
+ }
}
- do_system_soft($cmd) == 0
- and return 1;
+ $cmd and do_system_soft($cmd) == 0 and return 1;
return;
}
View
BIN website/camps-presentation-20110817/citypass-camps-index.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN website/camps-presentation-20110817/continuous-delivery-book-cover.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN website/camps-presentation-20110817/devcamps.org-home-smaller.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
562 website/camps-presentation-20110817/index.html
@@ -0,0 +1,562 @@
+<!DOCTYPE html PUBLIC "-//W4C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>Camps: Better Development Environments</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<!-- metadata -->
+<meta name="generator" content="S5" />
+<meta name="version" content="S5 1.3" />
+<meta name="presdate" content="20110817" />
+<meta name="author" content="Jon Jensen" />
+<meta name="company" content="End Point Corporation" />
+<!-- configuration parameters -->
+<meta name="defaultView" content="slideshow" />
+<meta name="controlVis" content="hidden" />
+<!-- configuration extensions -->
+<meta name="tranSitions" content="true" />
+<meta name="fadeDuration" content="50" />
+<meta name="incrDuration" content="50" />
+<!-- style sheet links -->
+<link rel="stylesheet" href="ui/advanced_utf/slides.css" type="text/css" media="projection" id="slideProj" />
+<link rel="stylesheet" href="ui/advanced_utf/outline.css" type="text/css" media="screen" id="outlineStyle" />
+<link rel="stylesheet" href="ui/advanced_utf/print.css" type="text/css" media="print" id="slidePrint" />
+<link rel="stylesheet" href="ui/advanced_utf/opera.css" type="text/css" media="projection" id="operaFix" />
+<!-- S5 JS -->
+<script src="ui/advanced_utf/slides.js" type="text/javascript"></script>
+</head>
+<body>
+
+<div class="layout">
+<div id="controls"><!-- DO NOT EDIT --></div>
+<div id="currentSlide"><!-- DO NOT EDIT --></div>
+<div id="header"></div>
+<div id="footer">
+<h1>Camps</h1>
+<h2>YAPC::Europe 2011</h2>
+</div>
+</div>
+
+
+<div class="presentation">
+
+
+<div class="slide">
+<h1>Camps</h1>
+<h2>Better web development environments</h2>
+<h3>Jon Jensen</h3>
+<h4>End Point Corporation</h4>
+<p>YAPC::Europe 2011, Rīga, Latvia<br />
+17 August 2011</p>
+</div>
+
+<div class="slide">
+ <h1>Some context</h1>
+ <ul>
+ <li>Web development focus</li>
+ <li>... on Unix-like platforms</li>
+ <li>Solve or reduce some universal problems</li>
+ <li>Small, medium, and large projects</li>
+ <li>... and small projects often grow larger</li>
+ <li>An internal project released as open source</li>
+ </ul>
+ <div class="handout">
+ <ul>
+ <li>I work primarily with web applications in ecommerce, but many of the lessons I’ll talk about apply to software development in general, not just web development.</li>
+ <li>I’m focusing on camps, which is a particular development system we created at End Point, but many lessons can be applied piecewise in any development environment.</li>
+ <li>The only true small-scale development is a single developer, working on a single project, that starts and stays small, and progresses fairly linearly. Anything more than this &mdash; two developers, one developer with a few simultaneous projects on the same codebase, a project that becomes important to its users, a project that grows beyond a weekend hack &mdash; needs the same infrastructure as any large-scale project. But usually doesn’t get it.</li>
+ <li>Camps are a good example of a common open source works: solving a real problem, finding that the solution applies in another place, then another, then generalizing it and releasing it for others to use. Camps are used extensively by End Point and our clients, and are fairly mature, but the camps open source project is still young.</li>
+ </ul>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Different makes a difference</h1>
+ <table border="0" cellpadding="0" cellspacing="5">
+ <tr>
+ <td>
+ <ul>
+ <li>Database schema</li>
+ <li>Database contents</li>
+ <li>Web server</li>
+ <li>Operating system/distro: version, 32- vs. 64-bit, libraries</li>
+ <li>Language version</li>
+ <li>Version of language modules</li>
+ <li>Application server versions</li>
+ </ul>
+ </td>
+ <td>
+ <img src="pad-small.png" width="230" height="317" />
+ </td>
+ </tr>
+ </table>
+ <div class="handout">
+ <p>“Different makes a difference” comes from the book <cite>Practices of an Agile Developer</cite>, item #21.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Antipattern: Production only</h1>
+ <ol>
+ <li>Set up new server or hosting account</li>
+ <li>Write code</li>
+ <li>Step on other developers’ toes</li>
+ <li>Deal with version control “later”</li>
+ <li>Go live</li>
+ <li>Now need somewhere to do development, but no time</li>
+ <li>Hack directly in production</li>
+ <li>Suffer</li>
+ </ol>
+ <div class="handout">
+ <p>Developers need a place to work. The easiest place to do this is by editing code on the production site. When you’re working on a new site, there is no production site yet, so what you’re working in is clearly the <em>development environment</em>. When it’s time for that to go live, everyone’s in a hurry, things are all set up, so you switch it live. From this moment on, where do you develop? It’s of course very dangerous to keep developing right there, but it solves the immediate problems, everyone’s busy, and you make comrpomises.</p>
+ <p>Frameworks that build in support for development and testing, such as Ruby on Rails, are a huge step forward and greatly reduce this problem if their conventions are followed, but they also introduce some new problems of their own which I’ll cover in a few minutes.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Yes, that still happens</h1>
+ <p>It’s not as common as it once was.</p>
+ <p>Modern web development frameworks provide some good conventions and defaults.</p>
+ <p>Even so, there are still problems.</p>
+</div>
+
+
+<div class="slide">
+ <h1>Antipattern: 1 staging environment</h1>
+ <ol>
+ <li>Application code in version control</li>
+ <li>Developers work on their own machines</li>
+ <li>Single staging environment</li>
+ <li>No https</li>
+ </ol>
+</div>
+
+
+<div class="slide">
+ <h1>Single staging consequences</h1>
+ <ul>
+ <li>Tedious local setup by each developer</li>
+ </ul>
+ <div class="handout">
+ <p>Every developer has tedious local setup to do before beginning any work, perhaps not too extensive if they’re all using the exact same OS, versions, etc., but that’s rare. Often it means several hours’ worth of setup, documenting custom steps, etc. This makes it harder to bring in new developers when needed, for them to change machines, and so on.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Single staging consequences</h1>
+ <ul>
+ <li>Trouble from OS, language, libraries, and database differences</li>
+ </ul>
+ <div class="handout">
+ <p>Are you using the same architecture (32- vs. 64-bit), operating system, libraries, web server, database, as the production system and as the other developers? It’s a huge time-sink to deal with differences between Ubuntu, RHEL, Debian, Fedora, and Mac OS X, development web server vs. Apache, database release differences (Postgres 8.3 vs. 9.0, or MySQL 4.1 vs. 5.0, etc.), different versions of libraries such as ImageMagick, language libraries, etc.</p>
+ <p>Each developer will likely have differences from each other and from staging &amp; production.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Single staging consequences</h1>
+ <ul>
+ <li>Development databases are mostly empty</li>
+ <li>Staging and production databases grow apart</li>
+ </ul>
+ <div class="handout">
+ <p>If you’re not using a recent near-identical copy of your production database, you’re developing against a toy.</p>
+ <p>A nearly-empty development database is nearly useless for testing things such as UI behavior with large lists, paging systems, searching, query performance, memory consuption, etc. As staging and production databases grow apart, the same problems affect staging.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Single staging consequences</h1>
+ <ul>
+ <li>Development stack has toy web server</li>
+ <li>No https</li>
+ <li>Web server rewrite rule changes are an afterthought, made manually in staging</li>
+ </ul>
+ <div class="handout">
+ <p>A different web server may serve different MIME types, handles uploads differently, different access control, deflate or gzip compression, redirects and rewrites, etc. Differences are often glossed over and people say “it’ll work in production”.</p>
+ <p>Web server configuration changes are as much as part of the application code as anything else, yet they’re often treated as an afterthought, unversioned and separate from app code, with no way to experiment in development.</p>
+ <p>Problems transitioning between http and https are found only in staging or production because nobody uses “unnecessary” https in development. It’s not about security -- it’s about keeping the environment the same.</p>
+ <p>You probably have some Apache redirects, rewrites, and subdomains set up, and they’re not reflected in the development webservert. You could set up Apache locally and do all this, but did you?</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Enough antipatterns</h1>
+ <p>Most web developers can probably name a dozen more.</p>
+</div>
+
+
+<div class="slide">
+ <h1>Camps to the rescue</h1>
+ <ul>
+ <li>Deal with complex sites.</li>
+ <li>Keep as much the same as possible between development, staging, and production.</li>
+ <li>Automate everything, make it easy.</li>
+ <li>Keep technical debt to a minimum.</li>
+ </ul>
+ <div class="handout">
+ <p>Most simple sites don’t stay simple. If they’re at all successful, there are more projects, more people, more time pressures. It’s better to plan for success.</p>
+ <p>Camps require some initial investment, force some discipline, and require a little occasional maintenance. The value comes out when you’re dealing with a system that actually matters. And the payoff usually comes in saved time and sanity, more quickly than you’d expect.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>What are camps?</h1>
+ <p>Camps: A system and set of conventions to easily manage parallel server-based development and staging environments, and keep them synchronized with the production environment.</p>
+ <p>Camps leverage popular open source tools and build upon them, for example, Git and Subversion.</p>
+ <p>Camps don’t normally use virtualization, just classic Unix user separation.</p>
+</div>
+
+
+<div class="slide">
+ <h1>Demo</h1>
+ <p align="center"><img src="citypass-camps-index.png" width="578" height="438" /></p>
+ <div class="handout">
+ <ul>
+ <li>Show the camp-index.</li>
+ <li>Note camp0, camp1 staging conventions.</li>
+ <li>ssh to the server as user jon.</li>
+ <li>Create a new simple camp.</li>
+ <li>Scroll back in screen to review what mkcamp did.</li>
+ <li>Reload camp-index to show the new camp in the list.</li>
+ <li>ls -lFa camp$x and look at httpd, pgsql, htdocs.</li>
+ <li>Show in “ps uxww” the different services per camp.</li>
+ <li>netstat -nl | grep $camp_number</li>
+ <li>In a Postgres camp, run psql_camp, “show port”, see that it’s a separate database cluster.</li>
+ <li>rmcamp --number=$x</li>
+ </ul>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Glance at a real camps system</h1>
+ <p>They have a lot going on:</p>
+ <ul>
+ <li>Many projects, 20+ camps</li>
+ <li>12 developers, designers, content editors</li>
+ <li>Various entrance links to each camp</li>
+ <li>2 staging camps</li>
+ </ul>
+ <p>Camp systems usually firewalled to keep search engines from indexing them.</p>
+ <div class="handout">
+ <p>Let’s look at a snapshot of the camp index from one company’s actual camp system.</p>
+ <p>Note the comments have links to RT ticket numbers in their private ticket tracking system.</p>
+ <p>A few of the 18 “developers” are actually business people or graphic designers. They’re less likely to do the version control work themselves, but they edit files in their camps and have a developer to the committing for them.</p>
+ <p>This camp index shows running camps in bold as a convenience. As we see, it’s typical for not all camps to be running at the same time, to conserve server resources when a project is back-burnered for a while.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Camps provide:</h1>
+ <ul>
+ <li>Easy creation and destruction of camps</li>
+ <li>A central web index of all camps</li>
+ <li>Access to all who should have it, whenever and wherever they want</li>
+ </ul>
+ <div class="handout">
+ <p>It’s surprisingly important to have a “home page” for all camps, so that anyone can easily see at a glance which developers are working where, and on what projects, and can easily click through to test things out.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Camps also provide:</h1>
+ <ul>
+ <li>Separate web server, database, and app server</li>
+ <li>One camp per project</li>
+ <li>Often more than one camp per developer</li>
+ <li>Staging sites are camps too</li>
+ <li>Development work gets backed up since it’s on the server</li>
+ </ul>
+</div>
+
+
+<div class="slide">
+ <h1>Version control</h1>
+ <ul>
+ <li>Version control is how code moves around</li>
+ <li>Can use version control even in production</li>
+ <li>Developers can still work locally if desired, optionally tethered to a camp</li>
+ </ul>
+ <div class="handout">
+ <p>Though it isn’t supposed to happen, files <em>do</em> get changed directly in production, either by the business folks or by developers. You can use the version control system to pull the changes into production, and the version control system alerts you to any conflicts and allows you to commit changes made there. And helps you to educate the people who made such changes so that most occurrences of this go away. But it also works fine to use a formal packaging and deployment system to push out code if you prefer.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Helpful practices</h1>
+ <ol>
+ <li>Use <tt>https</tt> in development to match production</li>
+ <li>Use subdomains in development so cookies are separate</li>
+ <li>Outgoing email intercept</li>
+ <li>Database migration scripts</li>
+ <li>Unit tests</li>
+ </ol>
+ <div class="handout">
+ <ol>
+ <li>Unless you use https in development, browser security warnings dealing with http vs. https appear only as surprises once code gets to production. Create your own CA certificate, use one of the free ones, or worst case, a self-signed certificate.</li>
+ <li>And there are pesky cookie bugs that are hard to reproduce but that keep coming back. It turns out that some of your development environments are stepping on each other’s cookies, and some development cookies are even messing up production. That “shouldn’t” matter because it just affects internal people, but those internal people take and process orders, do data entry, test your code and report bugs. Setting up a separate subdomain for each development environment solves the problem, but what a pain!</li>
+ <li>It needs to be just as easy to destroy a no-longer-needed camp as it was to create it. But that involves a little bit more than just deleting the files, so we’ll script it. The database, app server, and web server need to be shut down, the files removed, and the camp removed from the camps index.</li>
+ <li>Outgoing email is an important part of many web apps, and where the app server supports it, we have camps automatically configure it to intercept all outgoing email and reroute it to the developer. This makes testing easy, even with various recipients. If the app server doesn’t support it, there’s a daemon called “fakemail” that can intercept it server-wide.</li>
+ <li>Database migration scripts can be simple SQL, or use the migrations tools your framework provides.</li>
+ <li>Camps are an easy place to run your unit tests.</li>
+ </ol>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Language &amp; framework support</h1>
+ <p>Camps are in use by about 50 companies, and the camps system is language and framework agnostic. We’ve used them with:</p>
+ <ul>
+ <li>Perl + Interchange, CGI scripts</li>
+ <li>Ruby + Rails, Sinatra</li>
+ <li>PHP + Drupal, Magento, osCommerce</li>
+ <li>Python + Django</li>
+ </ul>
+</div>
+
+
+<div class="slide">
+ <h1>Workflow</h1>
+ <ul>
+ <li>Edit in a camp</li>
+ <li>Test</li>
+ <li>Commit</li>
+ <li>Approval (as your needs require)</li>
+ <li>Push</li>
+ <li>Pull and test in staging</li>
+ <li>Pull into production when desired</li>
+ <li>Apply any needed database migrations</li>
+ </ul>
+ <div class="handout">
+ <ul>
+ <li>Make a change.</li>
+ <li>git status; git diff</li>
+ <li>git commit -a; git push</li>
+ <li>View camp0; nothing changed yet.</li>
+ <li>Switch to staging user; cd camp0 &amp;&amp; git pull</li>
+ <li>Refresh camp0 display, now change should be there.</li>
+ </ul>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Challenges converting to camps</h1>
+ <p>Migrating into camps you’ll likely discover even more application spaghetti and goo than expected.</p>
+ <p>Code may be spread arbitrarily across various users and directories.</p>
+</div>
+
+
+<div class="slide">
+ <h1>Hardcoded delights</h1>
+ <p>You’ll need to relativize in files or the database:</p>
+ <ul>
+ <li>Database sources</li>
+ <li>Filesystem paths</li>
+ <li>URLs</li>
+ <li>Hostnames</li>
+ <li>Absolute-path symbolic links</li>
+ </ul>
+</div>
+
+
+<div class="slide">
+ <h1>Take out the trash</h1>
+ <ul>
+ <li>Old versions of code or docroot files</li>
+ <li>Manual backup files, copies of whole directories, zip archives</li>
+ <li>Editor droppings: *~, .*.swp, #*#</li>
+ <li>Windows or Mac junk: Thumbs.db, WS_FTP.LOG, __MACOSX</li>
+ <li>Sessions, logs, or temporary files intermixed with source</li>
+ </ul>
+ <div class="handout">
+ <p>All sorts of weird and little-known integration with services on other servers, long-forgotten scripts, cron jobs, and apps on the production and developent servers, etc., will come out as you install an existing app into camps. This is especially likely if you’re adding version control for the first time. There’s nothing you can do about it except muscle through it. It’s not because of camps; it’s because you’re trying to make order out of disorder.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Database trash too</h1>
+ <ul>
+ <li>Backups of database tables</li>
+ <li>Extra copies of entire databases</li>
+ <li>Defunct roles</li>
+ </ul>
+</div>
+
+
+<div class="slide">
+ <h1>Database handling</h1>
+ <p>Ideally use a complete, frequently updated clone of the full production database, but adapt:</p>
+ <ul>
+ <li>cron job to dump production nightly</li>
+ <li>Script to scrub data for confidentiality</li>
+ <li>Remove data if really necessary</li>
+ <li>If huge, consider using filesystem snapshots to clone (LVM, xfs, NetApp, etc.)</li>
+ <li>Worst case, maintain central toy database that all camps can use</li>
+ </ul>
+</div>
+
+
+<div class="slide">
+ <h1>Deployment challenges</h1>
+ <ul>
+ <li>Forgotten integration with other systems</li>
+ <li>cron jobs that need to be customized for camps</li>
+ <li>Office firewalls blocking outbound access typically used by camps:
+ <ul>
+ <li>http on TCP port 9000-9099</li>
+ <li>https on TCP port 9100-9199</li>
+ <li>PostgreSQL/MySQL on TCP port 8900-8999</li>
+ <li>sshd on standard TCP port 22 but may be blocked</li>
+ </ul>
+ </li>
+ </ul>
+ <div class="handout">
+ <p>You should expect the unexpected when it comes to firewalls. There may be a tight ingress and/or egress firewall on the camps server. The business’s various offices may have tight egress firewalls per location that don’t allow access to your camps server at all, or to ports 8999-9199, and it will take time to get their network administrator to open this up. Or set up VPN access to the camps server. IPv6 will reduce the need to use nonstandard ports.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Resource challenges</h1>
+ <p>Busy servers with many active camps may hit limits:</p>
+ <ul>
+ <li>RAM</li>
+ <li>semaphores (Apache)</li>
+ <li>shared buffers (Postgres)</li>
+ <li>disk space</li>
+ <li>CPU and I/O</li>
+ <li>100 camps (arbitrary, but real)</li>
+ </ul>
+ <div class="handout">
+ <p>In short, hardware is cheaper than developers. Camps optimize for developers and business users over hardware. For camps you also will want to invest in the server, while the client machines aren’t a big concern (as long as they run an ssh client and a web browser).</p>
+ <p>Because camps have their own running daemons for each part of the application stack, they use more RAM than a system that cuts corners. RAM’s cheap enough that you should just buy more. But it may be different for the business people to think that you’ll want <em>more</em> RAM in your development box than in production, or the same amount at least.</p>
+ <p>Each Apache daemon uses semaphores, and the default Linux kernel needs to be configured to have more. It’s easy to do.</p>
+ <p>Each Postgres daemon uses some shared buffers, and the kernel needs to be configured to have more. It’s also easy to do.</p>
+ <p>Get lots of cheap disk. You’re going to have a complete copy of the whole application stack: docroot, app, database. To save some disk space you can use symbolic links for large docroot segments such as images or downloads, if developers don’t routinely need to change those in camps. You can use writeable LVM atomic filesystem snapshots of a single database to make database copies take less space and refresh more quickly.</p>
+ <p>CPU and I/O may become a limiting factor during camp rebuilds (lots of file copying and database imports), or during performance testing of a development or staging site. Having developers update camp databases or create new camps after hours can help. Or just getting better hardware usually solves the problem.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Principles summary</h1>
+ <ol>
+ <li>Accessible</li>
+ <li>Separate</li>
+ <li>Automated</li>
+ <li>Current</li>
+ <li>Versioned</li>
+ <li>Identical</li>
+ <li>Easy</li>
+ <li>Fast</li>
+ </ol>
+ <div class="handout">
+ <p>I can summarize the many details of the previous slides with these guiding principles.</p>
+ <ol>
+ <li>Keeping camps accessible is actually the most important thing. If the business people can’t get to them, development happens in the shadows, the wrong problems get solved, other developers have no visibility, and working together is difficult.</li>
+ <li>Camps need to be separate from production, and from each other. We compromise for usability’s sake and keep one developer’s camps all in the same home directory so it’s easy to get at the files, but unless the developer chooses to intermingle them, they are all completely independent.</li>
+ <li>Automating camps was crucial. Anything that must be done manually is hard to make time for, and very annoying when it’s finally done. It puts burdens on those who know how things work, and if those who don’t know as well end up doing a task, they may break things for others. It doesn’t make sense to automate a one-off, but pretty much everything about camps is going to be repeated.</li>
+ <li>Having current code, data, and configurations is essential. Many people think it’s fine to do development with stale data, or a subset of the data, but this always leads to trouble. Displays don’t look the same with different data. Queries don’t perform the same with less data, or very different data.</li>
+ <li>Version control is wonderful. It doesn’t solve all problems, but it solves the problems it’s meant to solve very well. I don’t list it first only because in practice the higher-ranked items surprisingly turned out to be even more important -- but not sufficient!</li>
+ <li>Making the various environments as nearly identical as possible is one of the main lessons here. Most compromises for time, disk space, memory, or anything else turn out to be a mistake. When in doubt, make it identical.</li>
+ <li>If it’s not easy, people will usually not do it, or will do it incorrectly. People are busy. Things need to be done under unexpected time pressure. Plan for the unexpected by automating things, and making them easy. If you feel you’re spoiling yourself or your fellow developers, you’re probably on the right track, and will soon wonder how you lived without it.</li>
+ <li>If something takes too long, people will avoid doing it. Don’t live with slowness for too long without looking for ways to improve it.</li>
+ </ol>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Coordinating people</h1>
+ <p>Not part of camps per se, but essential:</p>
+ <ol>
+ <li>Email notification &amp; inline diffs of pushed commits</li>
+ <li>Use a ticket tracker</li>
+ <li>Use a wiki</li>
+ <li>Offer a web repository browser</li>
+ <li>Screen sharing with screen or tmux</li>
+ </ol>
+ <div class="handout">
+ <ol>
+ <li>Having an automated commit email notification in the version control system lets everyone quickly find out every time a commit is made. Adding an inline diff makes it trivial for developers to review each other’s code, hit reply to comment or ask questions, and be aware of changes that are being made.</li>
+ <li>It is hard to keep track of the open projects, bugs, feature enhancement requests, etc. until you set up a ticket tracker. The hardest thing is deciding which one to use! But it really doesn’t matter, compared to not having one at all.</li>
+ <li>Set up a wiki so that project documentation, people’s contact information, emergency procedures, etc. are in one location everyone knows about, and everyone can easily contribute to.</li>
+ <li>Offering developers a web interface to browse the version control repository makes the previously hidden visible. Less technical people can just as easily browse the change history and see what changed when, and whether a particular change has been committed yet or not. When using distributed version control, this isn’t quite as important since each cloned repository can be browsed independently, but it’s still helpful.</li>
+ <li>GNU screen and tmux are powerful tools in general, but especially the shared screen feature is excellent for pair programming, code review, and troubleshooting.</li>
+ </ol>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Related topics</h1>
+ <table border="0" cellpadding="0" cellspacing="5">
+ <tr>
+ <td>
+ <ul>
+ <li><a href="http://en.wikipedia.org/wiki/DevOps">DevOps</a> movement</li>
+ <li><a href="http://continuousdelivery.com/">Continuous Delivery book</a> by Jez Humble &amp; David Farley, 2011.</li>
+ </ul>
+ </td>
+ <td>
+ <img src="continuous-delivery-book-cover.jpg" width="160" height="211" />
+ </td>
+ </tr>
+ </table>
+ <div class="handout">
+ <p>Camps solve pieces of the puzzle, but camps are just part of the toolset you should use, and your own intelligence matters more than any of the tools in particular.</p>
+ </div>
+</div>
+
+
+<div class="slide">
+ <h1>Related projects</h1>
+ <ul>
+ <li><a href="http://projects.purpleatom.com/silver/0.2/">Silver</a></li>
+ <li><a href="http://vagrantup.com/">Vagrant</a>, <a href="http://www.perlbrew.pl/">perlbrew</a>, <a href="http://www.virtualenv.org/">virtualenv</a></li>
+ <li><a href="http://www.controltier.org/">ControlTier</a>, <a href="https://github.com/capistrano/capistrano#readme">Capistrano</a>, <a href="http://fabfile.org/">Fabric</a></li>
+ <li><a href="http://www.puppetlabs.com/">Puppet</a>, <a href="http://www.opscode.com/chef/">Chef</a>, <a href="http://rundeck.org/">RunDeck</a></li>
+ <li>Continuous Integration (<a href="http://hudson-ci.org/">Hudson</a>, <a href="http://jenkins-ci.org/">Jenkins</a>, etc.)</li>
+ <li><a href="http://stackable.com/">Stackable.com</a>, some other cloudy things</li>
+ </ul>
+</div>
+
+
+<div class="slide">
+ <h1>Further information</h1>
+ <ul>
+ <li>Camps website: <a href="http://www.devcamps.org/">www.devcamps.org</a></li>
+ </ul>
+
+ <p align="center"><a href="http://www.devcamps.org/"><img src="devcamps.org-home-smaller.png" width="300" height="254" /></a></p>
+
+ <ul>
+ <li>Twitter: <a href="https://twitter.com/jonjensen0">jonjensen0</a></li>
+ <li>email: <a href="mailto:jon@endpoint.com">jon@endpoint.com</a></li>
+ </ul>
+</div>
+
+
+</div>
+
+
+</body>
+</html>
+
+<!-- vim: set noet ts=2 sw=2 sts=2: -->
View
BIN website/camps-presentation-20110817/pad-small.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
1 website/camps-presentation-20110817/page.css
@@ -0,0 +1 @@
+@page { size: 1280px 800px }
View
BIN website/camps-presentation-20110817/ui/advanced_utf/backgrnd.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN website/camps-presentation-20110817/ui/advanced_utf/footer.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
22 website/camps-presentation-20110817/ui/advanced_utf/framing.css
@@ -0,0 +1,22 @@
+/* The following styles size, place, and layer the slide components.
+ Edit these if you want to change the overall slide layout.
+ The commented lines can be uncommented (and modified, if necessary)
+ to help you with the rearrangement process. */
+
+/* target = 1024x768 */
+
+div#header, div#footer, .slide {width: 100%; top: 0; left: 0;}
+div#header {top: 0; height: 3em; z-index: 1;}
+div#footer {top: auto; bottom: 0; height: 0.5em; z-index: 5;}
+.slide {top: 0; width: 92%; padding: 3.5em 4% 4%; z-index: 2; list-style: none;}
+div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;}
+div#controls form {text-align: right; width: 100%; margin: 0;}
+#currentSlide {position: absolute; width: 12%; left: 44%; bottom: 1em; z-index: 10;}
+html>body #currentSlide {position: fixed;}
+
+/*
+div#header {background: #FCC;}
+div#footer {background: #CCF;}
+div#controls {background: #BBD;}
+div#currentSlide {background: #FFC;}
+*/
View
BIN website/camps-presentation-20110817/ui/advanced_utf/header.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
7 website/camps-presentation-20110817/ui/advanced_utf/opera.css
@@ -0,0 +1,7 @@
+/* DO NOT CHANGE THESE unless you really want to break Opera Show */
+.slide {
+ visibility: visible !important;
+ position: static !important;
+ page-break-before: always;
+}
+#slide0 {page-break-before: avoid;}
View
13 website/camps-presentation-20110817/ui/advanced_utf/outline.css
@@ -0,0 +1,13 @@
+/* don't change this unless you want the layout stuff to show up in the outline view! */
+
+.hide, .layout div, #footer *, #controlForm * {display: none;}
+#footer, #controls, #controlForm, #navLinks, #sheet {display: block; visibility: visible; margin: 0; padding: 0;}
+#sheet {float: right; padding: 0.5em;}
+html>body #sheet {position: fixed; top: 0; right: 0;}
+
+/* making the outline look pretty-ish */
+
+#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;}
+#slide0 h1 {padding-top: 1.5em;}
+.slide h1 {margin: 1.5em 0 0; padding-top: 0.25em; border-top: 1px solid #888; border-bottom: 1px solid #AAA;}
+#sheet {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;}
View
BIN website/camps-presentation-20110817/ui/advanced_utf/pattern.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
94 website/camps-presentation-20110817/ui/advanced_utf/pretty.css
@@ -0,0 +1,94 @@
+/* Following are the presentation styles -- edit away! */
+
+body {background: #FFF url(backgrnd.png) fixed; color: #333; font-family: Tahoma, Arial, Helvetica, sans-serif; font-size: 2.25em;}
+a:link, a:visited {text-decoration: none; color: #C93;}
+
+h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;}
+h1 {text-shadow: 0.1em 0.1em 0.1em #333; background-color: transparent;}
+
+ul, pre {margin: 0; line-height: 1em;}
+html, body {margin: 0; padding: 0;}
+
+blockquote, q {font-style: italic;}
+blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em; text-align: center; font-size: 1em;}
+blockquote p {margin: 0;}
+blockquote i {font-style: normal;}
+blockquote b {display: block; margin-top: 0.5em; font-weight: normal; font-size: smaller; font-style: normal;}
+blockquote b i {font-style: italic;}
+
+img {border: 0;}
+kbd {font-weight: bold; font-size: 1em;}
+sup {font-size: smaller; line-height: 1px;}
+
+.slide code {padding: 2px 0.25em; font-weight: bold; color: #533;}
+.slide code.bad, code del {color: red;}
+.slide code.old {color: silver;}
+.slide pre {padding: 0; margin: 0.25em 0 0.5em 0.5em; color: #533; font-size: 90%;}
+.slide pre code {display: block;}
+.slide ul {margin-left: 5%; margin-right: 7%; list-style: disc;}
+.slide li {margin-top: 0.75em; margin-right: 0;}
+.slide ul ul {line-height: 1;}
+.slide ul ul li {margin: .2em; font-size: 85%; list-style: square;}
+.slide img.leader {display: block; margin: 0 auto;}
+
+div#header, div#footer {background: #fff; color: #AAA; font-family: Verdana, Arial, Helvetica, sans-serif;}
+div#header {background: transparent url(header.png) bottom; line-height: 1px;}
+div#footer {background: #999 url(footer.jpg) top; font-size: 0.5em; font-weight: bold; padding: 1em 0;}
+#footer h1, #footer h2 {border: none; display: block; padding: 0; position:absolute; bottom:0.5em;}
+#footer h1 {left:1em;}
+#footer h2 {right:1em; font-style: italic;}
+
+div.long {font-size: 0.75em;}
+.slide h1 {position: absolute; top: 0em; left: 0; z-index: 1; margin: 0; padding: 0.45em 0 0 25px; white-space: nowrap; font: bold 125%/1em Verdana, Arial, Helvetica, sans-serif; color: #DDD; background-color: transparent;}
+.slide h3 {font-size: 130%;}
+
+div#controls {position: absolute; left: 25%; bottom: 0; width: 50%; text-align: center; font: bold 1em Verdana, Arial, Helvetica, sans-serif;}
+html>body div#controls {background: transparent; position: fixed; padding: 0; top: auto; text-align: center;}
+#controls #sheet {display: none;}
+#controls #controlForm {height: 3.0em; text-align: center; vertical-align: middle;}
+#controls #navLinks {padding: 0 0 0.2em 0; margin: 0; background: url(pattern.png) gray bottom repeat-x; border: 2px outset gray; border-top-color: silver; border-left-color: silver; text-align: center; vertical-align: middle; white-space: nowrap;}
+#controls #navLinks a {text-shadow: #444 0.1em 0.1em 0.1em; padding: 0; margin: 0em 0.36em; background: transparent; border: none; color: white; cursor: pointer;}
+#controls #navLinks a:focus, #controls #navLinks a:hover {text-decoration: none; color: black !important;}
+#controls #navLinks a:link, #controls #navLinks a:visited {text-decoration: none; color: white;}
+#controls #navLinks .subLinks {display: inline; border: 1px inset gray; border-bottom-color: silver; border-right-color: silver; font-size: 75%; padding: 0 0.25em; margin: -0.2em 0 0 0; background-color: #666; text-align: center; vertical-align: middle; white-space: nowrap;}
+#controls #navLinks .subLinks a {text-shadow: none; font-weight: normal; padding: 0; margin: 0; border: none; color: white; cursor: pointer;}
+
+#jumplist, #volumelist {padding: 0; margin: 0; width: 1.5em; height: 2.25em; cursor: pointer;}
+
+#currentSlide {white-space: nowrap; text-align: center; margin-bottom: -0.5em; font-size: 0.5em; background-color: transparent; color: #999;}
+
+#guru {position: absolute; visibility: visible; left: 0px; top: 0px; padding: 4px; width: 99%; height: auto; text-align: center; background-color: black; z-index: 10;}
+#guru div {border: solid 3px red; padding: 4px; font-family: monospace; font-size: 60%; width: auto; height: auto; color: red; text-align: center;}
+
+#slide0 {padding-top: 3.5em; font-size: 90%;}
+#slide0 h1 {position: static; margin: 1em 0 0; padding: 0; font: bold 3em Verdana, Arial, Helvetica, sans-serif; text-shadow: 0.1em 0.1em 0.1em #666; white-space: normal; color: #222; background-color: transparent;}
+#slide0 h2 {font: bold italic 1em Arial, Helvetica, sans-serif; margin: 0.25em;}
+#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;}
+#slide0 h4 {margin-top: 0; font-size: 1em;}
+
+ul.urls {list-style: none; display: inline; margin: 0;}
+.urls li {display: inline; margin: 0;}
+.note {display: none;}
+.external {border-bottom: 1px dotted gray;}
+html>body .external {border-bottom: none;}
+.external:after {content: " \274F"; font-size: smaller; color: #999;}
+
+.incremental, .incremental *, .incremental *:after {color: #ccc; visibility: visible;}
+img.incremental, canvas.incremental {visibility: hidden;}
+.slide .current {color: #B02;}
+
+/* diagnostics
+li:after {content: " [" attr(class) "]"; color: #F88;}
+*/
+
+table.piechart, table.barchart, table.linechart { border-spacing: 0.3em 0.15em; }
+table.piechart tr th, table.barchart tr th, table.linechart tr th { white-space: nowrap; }
+table.piechart tr td, table.barchart tr td, table.linechart tr td { vertical-align: top; white-space: nowrap; }
+table.piechart tr td.col, table.barchart tr td.col, table.linechart tr td.col { border-bottom: 1px solid #555; border-right: 1px solid #555; }
+table.fs90 tr td, table.fs90 tr th, div.fs90, pre.fs90, p.fs90 ,ul.fs90 {font-size: 0.9em; }
+table.fs75 tr td, table.fs75 tr th, div.fs75, pre.fs75, p.fs75 ,ul.fs75 {font-size: 0.75em; }
+table.fs66 tr td, table.fs66 tr th, div.fs66, pre.fs66, p.fs66 ,ul.fs66 {font-size: 0.66em; }
+table.fs50 tr td, table.fs50 tr th, div.fs50, pre.fs50, p.fs50 ,ul.fs50 {font-size: 0.5em; }
+
+#soundmanager-debug {position:fixed; top:0px; right:0px; width:30em; height:20em; overflow:auto; border:1px solid red; padding:1em; margin:2em; font-family:"sans serif"; font-size: 12px;color: black; background-color:#f6f6f6; z-index: 100;}
+#soundmanager-debug code {font-size: 11px;}
View
3 website/camps-presentation-20110817/ui/advanced_utf/print.css
@@ -0,0 +1,3 @@
+/* The following rule is necessary to have all slides appear in print! DO NOT REMOVE IT! */
+.slide, ul, p {page-break-inside: avoid; visibility: visible !important;}
+h1 {page-break-after: avoid;}
View
8 website/camps-presentation-20110817/ui/advanced_utf/s5-core.css
@@ -0,0 +1,8 @@
+/* Do not edit or override these styles! The system will likely break if you do. */
+
+div#header, div#footer, div#controls, .slide {position: absolute;}
+html>body div#header, html>body div#footer, html>body div#controls, html>body .slide {position: fixed;}
+.handout, .notes, .hide {display: none;}
+.layout {display: block;}
+.slide, .hideme, .incremental {visibility: hidden;}
+#slide0 {visibility: visible;}
View
3 website/camps-presentation-20110817/ui/advanced_utf/slides.css
@@ -0,0 +1,3 @@
+@import url(s5-core.css); /* required to make the slide show run at all */
+@import url(framing.css); /* sets basic placement and size of slide components */
+@import url(pretty.css); /* stuff that makes the slides look better than blah */
View
2,665 website/camps-presentation-20110817/ui/advanced_utf/slides.js
2,665 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
29 website/camps-presentation-20110817/ui/audio_support/license.txt
@@ -0,0 +1,29 @@
+Software License Agreement (BSD License)
+
+Copyright (c) 2007, Scott Schiller (schillmania.com)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this
+ list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+* Neither the name of schillmania.com nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission from schillmania.com.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
BIN website/camps-presentation-20110817/ui/audio_support/null.mp3
Binary file not shown.
View
658 website/camps-presentation-20110817/ui/audio_support/soundmanager2.js
@@ -0,0 +1,658 @@
+/*
+ SoundManager 2: Javascript Sound for the Web.
+ --------------------------------------------------
+ http://www.schillmania.com/projects/soundmanager2/
+
+ Copyright (c) 2007, Scott Schiller. All rights reserved.
+ Code licensed under the BSD License:
+ http://www.schillmania.com/projects/soundmanager2/license.txt
+
+ Beta V2.0b.20070118
+*/
+
+function SoundManager(smURL,smID) {
+ var self = this;
+ this.enabled = false;
+ this.o = null;
+ this.url = (smURL||'ui/audio_support/soundmanager2.swf');
+ this.id = (smID||'sm2movie');
+ this.oMC = null;
+ this.sounds = [];
+ this.soundIDs = [];
+ this.allowPolling = true; // allow flash to poll for status update (required for "while playing", "progress" etc. to work.)
+ this.isIE = (navigator.userAgent.match(/MSIE/));
+ this.isSafari = (navigator.userAgent.match(/safari/i));
+ this._didAppend = false;
+ this._didInit = false;
+ this._disabled = false;
+ this.version = 'V2.0b.20070118';
+
+ this.defaultOptions = {
+ // -----------------
+ 'debugMode': false, // enable debug/status messaging, handy for development/troubleshooting - will be written to a DIV with an ID of "soundmanager-debug", you can use CSS to make it pretty
+ 'autoLoad': false, // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)
+ 'stream': true, // allows playing before entire file has loaded (recommended)
+ 'autoPlay': false, // enable playing of file as soon as possible (much faster if "stream" is true)
+ 'onid3': null, // callback function for "ID3 data is added/available"
+ 'onload': null, // callback function for "load finished"
+ 'whileloading': null, // callback function for "download progress update" (X of Y bytes received)
+ 'onplay': null, // callback for "play" start
+ 'whileplaying': null, // callback during play (position update)
+ 'onstop': null, // callback for "user stop"
+ 'onfinish': null, // callback function for "sound finished playing"
+ 'onbeforefinish': null, // callback for "before sound finished playing (at [time])"
+ 'onbeforefinishtime': 2000, // offset (milliseconds) before end of sound to trigger beforefinish (eg. 1000 msec = 1 second)
+ 'onbeforefinishcomplete':null, // function to call when said sound finishes playing
+ 'onjustbeforefinish':null, // callback for [n] msec before end of current sound
+ 'onjustbeforefinishtime':200, // [n] - if not using, set to 0 (or null handler) and event will not fire.
+ 'multiShot': true, // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time
+ 'pan': 0, // "pan" settings, left-to-right, -100 to 100
+ 'volume': 100, // self-explanatory. 0-100, the latter being the max.
+ // -----------------
+ 'foo': 'bar' // you don't need to change this one - it's actually an intentional filler.
+ }
+
+ // --- public methods ---
+
+ this.getMovie = function(smID) {
+ // return self.isIE?window[smID]:document[smID];
+ return self.isIE?window[smID]:(self.isSafari?document[smID+'-embed']:document.getElementById(smID+'-embed'));
+ }
+
+ this.loadFromXML = function(sXmlUrl) {
+ try {
+ self.o._loadFromXML(sXmlUrl);
+ } catch(e) {
+ self._failSafely();
+ return true;
+ }
+ }
+
+ this.createSound = function(oOptions) {
+ if (!self._didInit) throw new Error('soundManager.createSound(): Not loaded yet - wait for soundManager.onload() before calling sound-related methods');
+ if (arguments.length==2) {
+ // function overloading in JS! :) ..assume simple createSound(id,url) use case
+ oOptions = {'id':arguments[0],'url':arguments[1]}
+ }
+ var thisOptions = self._mergeObjects(oOptions);
+ self._writeDebug('soundManager.createSound(): "<a href="#" onclick="soundManager.play(\''+thisOptions.id+'\');return false" title="play this sound">'+thisOptions.id+'</a>" ('+thisOptions.url+')');
+ if (self._idCheck(thisOptions.id,true)) {
+ self._writeDebug('sound '+thisOptions.id+' already defined - exiting');
+ return false;
+ }
+ self.sounds[thisOptions.id] = new SMSound(self,thisOptions);
+ self.soundIDs[self.soundIDs.length] = thisOptions.id;
+ try {
+ self.o._createSound(thisOptions.id,thisOptions.onjustbeforefinishtime);
+ } catch(e) {
+ self._failSafely();
+ return true;
+ }
+ if (thisOptions.autoLoad || thisOptions.autoPlay) self.sounds[thisOptions.id].load(thisOptions);
+ if (thisOptions.autoPlay) self.sounds[thisOptions.id].playState = 1; // we can only assume this sound will be playing soon.
+ }
+
+ this.load = function(sID,oOptions) {
+ if (!self._idCheck(sID)) return false;
+ self.sounds[sID].load(oOptions);
+ }
+
+ this.unload = function(sID) {
+ if (!self._idCheck(sID)) return false;
+ self.sounds[sID].unload();
+ }
+
+ this.play = function(sID,oOptions) {
+ if (!self._idCheck(sID)) {
+ if (typeof oOptions != 'Object') oOptions = {url:oOptions}; // overloading use case: play('mySound','/path/to/some.mp3');
+ if (oOptions && oOptions.url) {
+ // overloading use case, creation + playing of sound: .play('someID',{url:'/path/to.mp3'});
+ self._writeDebug('soundController.play(): attempting to create "'+sID+'"');
+ oOptions.id = sID;
+ self.createSound(oOptions);
+ } else {
+ return false;
+ }
+ }
+ self.sounds[sID].play(oOptions);
+ }
+
+ this.start = this.play; // just for convenience
+
+ this.setPosition = function(sID,nMsecOffset) {
+ if (!self._idCheck(sID)) return false;
+ self.sounds[sID].setPosition(nMsecOffset);
+ }
+
+ this.stop = function(sID) {
+ if (!self._idCheck(sID)) return false;
+ self._writeDebug('soundManager.stop('+sID+')');
+ self.sounds[sID].stop();
+ }
+
+ this.stopAll = function() {
+ for (var oSound in self.sounds) {
+ if (oSound instanceof SMSound) oSound.stop(); // apply only to sound objects
+ }
+ }
+
+ this.pause = function(sID) {
+ if (!self._idCheck(sID)) return false;
+ self.sounds[sID].pause();
+ }
+
+ this.resume = function(sID) {
+ if (!self._idCheck(sID)) return false;
+ self.sounds[sID].resume();
+ }
+
+ this.togglePause = function(sID) {
+ if (!self._idCheck(sID)) return false;
+ self.sounds[sID].togglePause();
+ }
+
+ this.setPan = function(sID,nPan) {
+ if (!self._idCheck(sID)) return false;
+ self.sounds[sID].setPan(nPan);
+ }
+
+ this.setVolume = function(sID,nVol) {
+ if (!self._idCheck(sID)) return false;
+ self.sounds[sID].setVolume(nVol);
+ }
+
+ this.setPolling = function(bPolling) {
+ if (!self.o || !self.allowPolling) return false;
+ self._writeDebug('soundManager.setPolling('+bPolling+')');
+ self.o._setPolling(bPolling);
+ }
+
+ this.disable = function() {
+ // destroy all functions
+ if (self._disabled) return false;
+ self._disabled = true;
+ self._writeDebug('soundManager.disable(): Disabling all functions - future calls will return false.');
+ for (var i=self.soundIDs.length; i--;) {
+ self._disableObject(self.sounds[self.soundIDs[i]]);
+ }
+ self.initComplete(); // fire "complete", despite fail
+ self._disableObject(self);
+ }
+
+ this.getSoundById = function(sID,suppressDebug) {
+ if (!sID) throw new Error('SoundManager.getSoundById(): sID is null/undefined');
+ var result = self.sounds[sID];
+ if (!result && !suppressDebug) {
+ self._writeDebug('"'+sID+'" is an invalid sound ID.');
+ // soundManager._writeDebug('trace: '+arguments.callee.caller);
+ }
+ return result;
+ }
+
+ this.onload = function() {
+ onloadSM2();
+ // window.onload() equivalent for SM2, ready to create sounds etc.
+ // this is a stub - you can override this in your own external script, eg. soundManager.onload = function() {}
+ // soundManager._writeDebug('<em>Warning</em>: soundManager.onload() is undefined.');
+ }
+
+ this.onerror = function() {
+ onerrorSM2();
+ // stub for user handler, called when SM2 fails to load/init
+ }
+
+ // --- "private" methods ---
+
+ this._idCheck = this.getSoundById;
+
+ this._disableObject = function(o) {
+ for (var oProp in o) {
+ if (typeof o[oProp] == 'function') o[oProp] = function(){return false;}
+ }
+ oProp = null;
+ }
+
+ this._failSafely = function() {
+ // exception handler for "object doesn't support this property or method"
+ var flashCPLink = 'http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html';
+ var fpgssTitle = 'You may need to whitelist this location/domain eg. file://C:/ or C:/ or mysite.com, or set ALWAYS ALLOW under the Flash Player Global Security Settings page. Note that this seems to apply only to file system viewing.';
+ var flashCPL = '<a href="'+flashCPLink+'" title="'+fpgssTitle+'">view/edit</a>';
+ var FPGSS = '<a href="'+flashCPLink+'" title="Flash Player Global Security Settings">FPGSS</a>';
+ if (!self._disabled) {
+ self._writeDebug('soundManager: JS-&gt;Flash communication failed. Possible causes: flash/browser security restrictions ('+flashCPL+'), insufficient browser/plugin support, or .swf not found');
+ self._writeDebug('Verify that the movie path of <em>'+self.url+'</em> is correct (<a href="'+self.url+'" title="If you get a 404/not found, fix it!">test link</a>)');
+ if (self._didAppend) {
+ if (!document.domain) {
+ self._writeDebug('Loading from local file system? (document.domain appears to be null, this location may need whitelisting by '+FPGSS+')');
+ self._writeDebug('Possible security/domain restrictions ('+flashCPL+'), should work when served by http on same domain');
+ }
+ // self._writeDebug('Note: Movie added via JS method, static object/embed in-page markup method may work instead.');
+ }
+ self.disable();
+ }
+ }
+
+ this._createMovie = function(smID,smURL) {
+ var useDOM = !self.isIE; // IE needs document.write() to work, long story short - lame
+ if (window.location.href.indexOf('debug=1')+1) self.defaultOptions.debugMode = true; // allow force of debug mode via URL
+ var html = ['<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="16" height="16" id="'+smID+'"><param name="movie" value="'+smURL+'"><param name="quality" value="high"><param name="allowScriptAccess" value="always" /></object>','<embed name="'+smID+'-embed" id="'+smID+'-embed" src="'+smURL+'" width="1" height="1" quality="high" allowScriptAccess="always" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></embed>'];
+ var debugID = 'soundmanager-debug';
+ var debugHTML = '<div id="'+debugID+'" style="display:'+(self.defaultOptions.debugMode?'block':'none')+'"></div>';
+ if (useDOM) {
+ self.oMC = document.createElement('div');
+ self.oMC.className = 'movieContainer';
+ // "hide" flash movie
+ self.oMC.style.position = 'absolute';
+ self.oMC.style.left = '-256px';
+ self.oMC.style.width = '1px';
+ self.oMC.style.height = '1px';
+ self.oMC.innerHTML = html[self.isIE?0:1];
+ var oTarget = (!self.isIE && document.body?document.body:document.getElementsByTagName('div')[0]);
+ oTarget.appendChild(self.oMC); // append to body here can throw "operation aborted" under IE
+ if (!document.getElementById(debugID)) {
+ var oDebug = document.createElement('div');
+ oDebug.id = debugID;
+ oDebug.style.display = (self.defaultOptions.debugMode?'block':'none');
+ oTarget.appendChild(oDebug);
+ }
+ oTarget = null;
+ } else {
+ // IE method - local file system, may cause strange JS error at line 53?
+ // I hate this method, but it appears to be the only one that will work (createElement works, but JS->Flash communication oddly fails when viewing locally.)
+ // Finally, IE doesn't do application/xhtml+xml yet (thus document.write() is still minimally acceptable here.)
+ if (document.getElementById(debugID)) debugHTML = ''; // avoid overwriting
+ document.write('<div style="position:absolute;left:-256px;top:-256px;width:1px;height:1px" class="movieContainer">'+html[self.isIE?0:1]+'</div>'+debugHTML);
+ }
+ self._didAppend = true;
+ self._writeDebug('-- SoundManager 2 Version '+self.version.substr(1)+' --');
+ self._writeDebug('soundManager._createMovie(): trying to load <a href="'+smURL+'" title="Test this link (404=bad)">'+smURL+'</a>');
+ }
+
+ this._writeDebug = function(sText) {
+ if (!self.defaultOptions.debugMode) return false;
+ var sDID = 'soundmanager-debug';
+ try {
+ var o = document.getElementById(sDID);
+ if (!o) {
+ // attempt to create soundManager debug element
+ var oNew = document.createElement('div');
+ oNew.id = sDID;
+ // o = document.body.appendChild(oNew);
+ o = null;
+ if (!o) return false;
+ }
+ var p = document.createElement('div');
+ p.innerHTML = sText;
+ // o.appendChild(p); // top-to-bottom
+ o.insertBefore(p,o.firstChild); // bottom-to-top
+ } catch(e) {
+ // oh well
+ }
+ o = null;
+ }
+
+ this._debug = function() {
+ self._writeDebug('soundManager._debug(): sounds by id/url:');
+ for (var i=0,j=self.soundIDs.length; i<j; i++) {
+ self._writeDebug(self.sounds[self.soundIDs[i]].sID+' | '+self.sounds[self.soundIDs[i]].url);
+ }
+ }
+
+ this._mergeObjects = function(oMain,oAdd) {
+ // non-destructive merge
+ var o1 = oMain;
+ var o2 = (typeof oAdd == 'undefined'?self.defaultOptions:oAdd);
+ for (var o in o2) {
+ if (typeof o1[o] == 'undefined') o1[o] = o2[o];
+ }
+ return o1;
+ }
+
+ this.createMovie = function(sURL) {
+ self._writeDebug('soundManager.createMovie('+(sURL||'')+')');
+ if (sURL) self.url = sURL;
+ self._initMovie();
+ }
+
+ this._initMovie = function() {
+ // attempt to get, or create, movie
+ if (self.o) return false; // pre-init may have fired this function before window.onload(), may already exist
+ self.o = self.getMovie(self.id); // try to get flash movie (inline markup)
+ if (!self.o) {
+ // try to create
+ self._createMovie(self.id,self.url);
+ self.o = self.getMovie(self.id);
+ }
+ if (!self.o) {
+ self._writeDebug('SoundManager(): Could not find object/embed element. Sound will be disabled.');
+ self.disable();
+ } else {
+ self._writeDebug('SoundManager(): Got '+self.o.nodeName+' element ('+(self._didAppend?'created via JS':'static HTML')+')');
+ }
+ }
+
+ this.initComplete = function() {
+ if (self._didInit) return false;
+ self._didInit = true;
+ self._writeDebug('-- SoundManager 2 '+(self._disabled?'failed to load':'loaded')+' ('+(self._disabled?'security/load error':'OK')+') --');
+ if (self._disabled) {
+ self._writeDebug('soundManager.initComplete(): calling soundManager.onerror()');
+ self.onerror.apply(window);
+ return false;
+ }
+ self._writeDebug('soundManager.initComplete(): calling soundManager.onload()');
+ try {
+ // call user-defined "onload", scoped to window
+ self.onload.apply(window);
+ } catch(e) {
+ // something broke (likely JS error in user function)
+ self._writeDebug('soundManager.onload() threw an exception: '+e.message);
+ throw e; // (so browser/console gets message)
+ }
+ self._writeDebug('soundManager.onload() complete');
+ }
+
+ this.init = function() {
+ // called after onload()
+ self._initMovie();
+ // event cleanup
+ if (window.removeEventListener) {
+ window.removeEventListener('load',self.beginInit,false);
+ } else if (window.detachEvent) {
+ window.detachEvent('onload',self.beginInit);
+ }
+ try {
+ self.o._externalInterfaceTest(); // attempt to talk to Flash
+ self._writeDebug('Flash ExternalInterface call (JS -&gt; Flash) succeeded.');
+ if (!self.allowPolling) self._writeDebug('Polling (whileloading/whileplaying support) is disabled.');
+ self.setPolling(true);
+ self.enabled = true;
+ } catch(e) {
+ self._failSafely();
+ self.initComplete();
+ return false;
+ }
+ self.initComplete();
+ }
+
+ this.beginInit = function() {
+ self._initMovie();
+ setTimeout(self.init,1000); // some delay required, otherwise JS<->Flash/ExternalInterface communication fails under non-IE (?!)
+ }
+
+ this.destruct = function() {
+ if (self.isSafari) {
+ /* --
+ Safari 1.3.2 (v312.6)/OSX 10.3.9 and perhaps newer will crash if a sound is actively loading when user exits/refreshes/leaves page
+ (Apparently related to ExternalInterface making calls to an unloading/unloaded page?)
+ Unloading sounds (detaching handlers and so on) may help to prevent this
+ -- */
+ for (var i=self.soundIDs.length; i--;) {
+ if (self.sounds[self.soundIDs[i]].readyState == 1) self.sounds[self.soundIDs[i]].unload();
+ }
+ }
+ self.disable();
+ // self.o = null;
+ // self.oMC = null;
+ }
+
+}
+
+function SMSound(oSM,oOptions) {
+ var self = this;
+ var sm = oSM;
+ this.sID = oOptions.id;
+ this.url = oOptions.url;
+ this.options = sm._mergeObjects(oOptions);
+ this.id3 = {
+ /*
+ Name/value pairs set via Flash when available - see reference for names:
+ http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00001567.html
+ (eg., this.id3.songname or this.id3['songname'])
+ */
+ }
+
+ self.resetProperties = function(bLoaded) {
+ self.bytesLoaded = null;
+ self.bytesTotal = null;
+ self.position = null;
+ self.duration = null;
+ self.durationEstimate = null;
+ self.loaded = false;
+ self.loadSuccess = null;
+ self.playState = 0;
+ self.paused = false;
+ self.readyState = 0; // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success
+ self.didBeforeFinish = false;
+ self.didJustBeforeFinish = false;
+ }
+
+ self.resetProperties();
+
+ // --- public methods ---
+
+ this.load = function(oOptions) {
+ self.loaded = false;
+ self.loadSuccess = null;
+ self.readyState = 1;
+ self.playState = (oOptions.autoPlay||false); // if autoPlay, assume "playing" is true (no way to detect when it actually starts in Flash unless onPlay is watched?)
+ var thisOptions = sm._mergeObjects(oOptions);
+ if (typeof thisOptions.url == 'undefined') thisOptions.url = self.url;
+ try {
+ sm._writeDebug('loading '+thisOptions.url);
+ sm.o._load(self.sID,thisOptions.url,thisOptions.stream,thisOptions.autoPlay,thisOptions.whileloading?1:0);
+ } catch(e) {
+ sm._writeDebug('SMSound().load(): JS-&gt;Flash communication failed.');
+ }
+ }
+
+ this.unload = function() {
+ // Flash 8/AS2 can't "close" a stream - fake it by loading an empty MP3
+ sm._writeDebug('SMSound().unload()');
+ self.setPosition(0); // reset current sound positioning
+ sm.o._unload(self.sID,'ui/audio_support/null.mp3');
+ // reset load/status flags
+ self.resetProperties();
+ }
+
+ this.play = function(oOptions) {
+ if (!oOptions) oOptions = {};
+
+ // --- TODO: make event handlers specified via oOptions only apply to this instance of play() (eg. onfinish applies but will reset to default on finish)
+ if (oOptions.onfinish) self.options.onfinish = oOptions.onfinish;
+ if (oOptions.onbeforefinish) self.options.onbeforefinish = oOptions.onbeforefinish;
+ if (oOptions.onjustbeforefinish) self.options.onjustbeforefinish = oOptions.onjustbeforefinish;
+ // ---
+
+ var thisOptions = sm._mergeObjects(oOptions);
+ if (self.playState == 1) {
+ // var allowMulti = typeof oOptions.multiShot!='undefined'?oOptions.multiShot:sm.defaultOptions.multiShot;