# HG changeset patch
# User Nathan Phillip Brink 
# Date 2010-10-02 01:49:41
# Node ID 6b24e9820611856add9f70939ddde64f1b6baf01
# Parent  264ef5810d61c33332cdb83c594adfae0e39f0f4
Support pastebin-style referencing of one's saved schedules: each schedule is now assigned a global identification number and is accessible using that number. This commit requires y'all to delete your old session cookie because the storage format of the session cookie has changed.
diff --git a/.hgignore b/.hgignore
new file mode 100644
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,7 @@
+style: regex
+
+# ignore all saved schedules, but keep track of the .keep file.
+^saved_schedules/[^.]
+
+# ignore common unwanted suffixes
+(~|\.orig|\.rej)$
diff --git a/class.schedule.php b/class.schedule.php
--- a/class.schedule.php
+++ b/class.schedule.php
@@ -22,6 +22,12 @@ class Schedule
   private $storage;				// Integer array of valid schedules
   private $title;
   /**
+   * \brief
+   *   My global identification number. Not defined until the schedule
+   *   is processed and first saved.
+   */
+  private $id;
+  /**
    * The input format of the sections. Only used for the UI. Valid
    * values are 'numerous' for custom, 'numbered' for numeric, and 'lettered' for
    * alphabetical.
@@ -300,11 +306,11 @@ class Schedule
       echo '});'; /* Close document.ready */
       echo 'window.print();
 			      ';
-      echo 'Select Schedules to Print :: Return to normal view :: Home
';
-      echo '';
+      echo 'Select Schedules to Print :: Return to normal view :: Home
';
+      echo '';
     }
     else {
-      echo 'Print :: Home
';
+      echo 'id_get() . '&print=all">Print :: Home
';
     }		
 
     if($this->nPermutations > 0)
@@ -575,18 +581,7 @@ class Schedule
     }
 
     /* edit button */
-    if (!isset($savedkey))
-      {
-	if (isset($_REQUEST['savedkey']))
-	  $savedkey = (int)$_REQUEST['savedkey'];
-	else
-	  /*
-	   * if this is a new saved schedule, it'll be the
-	   * next item added to $_SESSION['saved']
-	   */
-	  $savedkey = max(array_keys($_SESSION['saved'])) + 1;
-      }
-    echo '';
+    echo '';
 
     echo "There were a total of " . $this->possiblePermutations . " possible permutations. Only " . $this->nPermutations . " permutations had no class conflicts.
";
 
@@ -649,4 +644,24 @@ class Schedule
   {
     return $this->classStorage[$class_key];
   }
+
+  /**
+   * \brief
+   *   Set my global ID.
+   *
+   * Only to be called by schedule_store_store().
+   */
+  function id_set($id)
+  { 
+    $this->id = $id;
+  }
+
+  /*
+   * \brief
+   *   Get my global ID.
+   */
+  function id_get()
+  {
+    return $this->id;
+  }
 }
diff --git a/inc/class.page.php b/inc/class.page.php
--- a/inc/class.page.php
+++ b/inc/class.page.php
@@ -1,8 +1,8 @@
 ';
-	if(isset($session['saved']) && count($session['saved']) > 0){
-		echo 'Saved Schedules:
';
-		foreach($session['saved'] as $key => $schedule){
-			$sch = unserialize($schedule);
-			echo "#" . ($key + 1) . " - " . $sch->getName()
-			  . " - 
view" .' 
edit '
-			  . "
delete"
-			  . "
\n";
-		}
-		echo '
 ';
-	}
-       echo '
';
+  public function showSavedScheds($session)
+  {
+    echo '';
+    if (isset($session['saved']) && count($session['saved']) > 0)
+      {
+	echo '
Saved Schedules:
';
+	foreach($session['saved'] as $key => $name)
+	  {
+	    echo '
#' . $key . ":\n "
+	      . htmlentities($name)
+	      . ' 
edit'
+	      . ' 
delete'
+	      . "
\n";
+	  }
+	echo '
 ';
+      }
+    echo '';
+  }
+
+  /**
+   * \brief
+   *   Display a 404 page and halt the PHP interpreter.
+   *
+   * This function does not return. It handles the creation of a Page
+   * class with 404-ish stuff and then calls exit() after flushing the
+   * page out to the user.
+   *
+   * \param $message
+   *   A message consisting of valid XHTML to display to the user in
+   *   the 404 page.
+   */
+  public static function show_404($message = 'I couldn\'t find what you were looking for :-/.')
+  {
+    $page_404 = new Page('404: Content Not Found');
+
+    echo "404: Content Not Found
\n"
+      . "\n"
+      . '  ' . $message . "\n"
+      . "
\n";
+
+    $page_404->foot();
+
+    exit();
+  }
 }
-
-}
diff --git a/inc/schedule_store.inc b/inc/schedule_store.inc
new file mode 100644
--- /dev/null
+++ b/inc/schedule_store.inc
@@ -0,0 +1,157 @@
+
+ *
+ * This file is a part of slate_permutate.
+ *
+ * slate_permutate is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * slate_permutate is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with slate_permutate.  If not, see .
+ */
+
+require_once('class.schedule.php');
+
+/**
+ * \brief
+ *   Initialize a schedule_store.
+ *
+ * \param $dir
+ *   Directory to use as the schedule storage.
+ * \return
+ *   A schedule_store handle or NULL on failure.
+ */
+function schedule_store_init($dir = 'saved_schedules')
+{
+  $schedule_store = array();
+
+  if (!is_dir($dir) || !is_writable($dir))
+    {
+      error_log('I can\'t write to ' . $dir . ' or it is not a directory!');
+      return NULL;
+    }
+
+  $schedule_store['dir'] = realpath($dir);
+
+  return $schedule_store;
+}
+
+/**
+ * \brief
+ *   Store a saved schedule into the schedule storage.
+ *
+ * \param $schedule_store
+ *   The schedule_store handle from schedule_store_init().
+ * \param $schedule
+ *   The schedule object of type Schedule.
+ * \return
+ *   The newly-saved schedules global ID (numeric) or NULL on error.
+ */
+function schedule_store_store($schedule_store, $schedule)
+{
+  $tempfile_name = tempnam($schedule_store['dir'], 'sch');
+
+  $new_schedule_id_name = tempnam($schedule_store['dir'], 'id');
+  $new_schedule_id_file = fopen($new_schedule_id_name, 'wb');
+
+  _schedule_store_flock_grab($schedule_store, LOCK_EX);
+  /* if the file doesn't exist, we'll end up with a value of 1 for our first entry. */
+  $schedule_id = (int)file_get_contents($schedule_store['dir'] . DIRECTORY_SEPARATOR . 'lastid');
+  $new_schedule_id = $schedule_id + 1;
+  fwrite($new_schedule_id_file, $new_schedule_id);
+  fclose($new_schedule_id_file);
+  rename($new_schedule_id_name, $schedule_store['dir'] . DIRECTORY_SEPARATOR . 'lastid');
+  _schedule_store_flock_release($schedule_store);
+
+  /* we need to serialize the schedule _after_ giving it an ID */
+  $schedule->id_set($new_schedule_id);
+  file_put_contents($tempfile_name, serialize($schedule));
+
+  rename($tempfile_name, $schedule_store['dir'] . DIRECTORY_SEPARATOR . $new_schedule_id);
+
+  return $new_schedule_id;
+}
+
+/**
+ * \brief
+ *   Retrieve a stored saved schedule from the schedule storage.
+ *
+ * \param $schedule_store
+ *   The schedule_store handle from which to retrieve the saved
+ *   schedule.
+ * \param $schedule_id
+ *   The saved schedule's globally-accessible ID. This value must have
+ *   been returned from schedule_store_store() at one point.
+ * \return
+ *   A Schedule object whose ID was $schedule_id or NULL if
+ *   $schedule_id is an invalid or not-yet-allocated schedule
+ *   identifier.
+ */
+function schedule_store_retrieve($schedule_store, $schedule_id)
+{
+  if (strcmp($schedule_id, (int)$schedule_id))
+    return NULL;
+  $schedule_id = (int)$schedule_id;
+
+  $schedule_serialized = file_get_contents($schedule_store['dir'] . DIRECTORY_SEPARATOR . $schedule_id);
+  if ($schedule_serialized === FALSE)
+    return NULL;
+
+  $schedule = unserialize($schedule_serialized);
+  if ($schedule === FALSE)
+    return NULL;
+  return $schedule;
+}
+
+/**
+ * \brief
+ *   Delete a saved schedule.
+ *
+ * \param $schedule_store
+ *   The store from which to delete the schedule.
+ * \param $schedule_id
+ *   The identifier of the schedule to delete.
+ */
+function schedule_store_delete($schedule_store, $schedule_id)
+{
+  $remove_filename = $schedule_store['dir'] . DIRECTORY_SEPARATOR . $schedule_id;
+  /* avoid an E_WARNING if the file doesn't exist */
+  if (file_exists($remove_filename))
+    remove($remove_filename);
+}
+
+/**
+ * \brief
+ *   Obtains a lock on the /lastid file in the schedule_store.
+ *
+ * \see _schedule_store_flock_release().
+ *
+ * \param $schedule_store
+ *   The schedule_store instance we're working with.
+ * \param $operation
+ *   Which flock() operation to perform: valid are LOCK_SH and LOCK_EX.
+ */
+function _schedule_store_flock_grab(&$schedule_store, $operation)
+{
+  $schedule_store['lastid_flock_file'] = fopen($schedule_store['dir'] . DIRECTORY_SEPARATOR . 'lastid.flock', 'c');
+  return flock($schedule_store['lastid_flock_file'], $operation);
+}
+
+/**
+ * \brief
+ *   Release a lock grabbed with _schedule_store_flock_grab().
+ */
+function _schedule_store_flock_release(&$schedule_store)
+{
+  flock($schedule_store['lastid_flock_file'], LOCK_UN);
+  fclose($schedule_store['lastid_flock_file']);
+  unset($schedule_store['lastid_flock_file']);
+}
diff --git a/input.php b/input.php
--- a/input.php
+++ b/input.php
@@ -4,18 +4,18 @@ include_once 'class.schedule.php';
 include_once 'class.class.php';
 include_once 'class.section.php';
 include_once 'inc/class.page.php';
+require_once('inc/schedule_store.inc');
 
 $scripts = array('jQuery', 'jQueryUI', 'jValidate','schedInput');
 $inputPage = new page('Scheduler', $scripts, FALSE);
 
+$schedule_store = FALSE;
 $sch = FALSE;
-if (isset($_REQUEST['savedkey']) && isset($_SESSION['saved']))
+if (isset($_REQUEST['s']))
   {
-    $savedkey = (int)$_REQUEST['savedkey'];
-    if (isset($_SESSION['saved'][$savedkey]))
-      {
-	$sch = unserialize($_SESSION['saved'][$savedkey]);
-      }
+    $schedule_store = schedule_store_init();
+    $schedule_id = (int)$_REQUEST['s'];
+    $sch = schedule_store_retrieve($schedule_store, $schedule_id);
   }
 
 if ($sch)
diff --git a/process.php b/process.php
--- a/process.php
+++ b/process.php
@@ -2,6 +2,8 @@
 
 session_start();
 
+require_once('inc/schedule_store.inc');
+require_once('inc/class.page.php');
 include_once 'class.schedule.php';
 include_once 'class.class.php';
 include_once 'class.section.php';
@@ -55,23 +57,36 @@ function prettyTime($time){
 	return substr($time,0,strlen($time)-2) . ":" . substr($time,strlen($time)-2, strlen($time));
 }
 
-$DEBUG = false;
-if(isset($_GET['debug']))
-	$DEBUG = $_GET['debug'];
+$DEBUG = FALSE;
+if (isset($_GET['debug']))
+  $DEBUG = $_GET['debug'];
 
-if(!$DEBUG){
+$schedule_store = schedule_store_init();
 
-	if(isset($_GET['savedkey'])){
-		$savedSched = unserialize($_SESSION['saved'][$_GET['savedkey']]);
-		$savedSched->writeoutTables();
-	}
-	else if(isset($_GET['delsaved'])){
-		$_SESSION['saved'][$_GET['delsaved']] = '';
-		$_SESSION['saved'] = array_filter($_SESSION['saved']); // Remove null entries
-              header( 'Location: input.php' ) ;
+if(!$DEBUG)
+  {
+    if(isset($_GET['s']))
+      {
+	$savedSched = schedule_store_retrieve($schedule_store, $_GET['s']);
+	if ($savedSched)
+	  $savedSched->writeoutTables();
+	else
+	  Page::show_404('Unable to find a saved schedule with an ID of ' . $_GET['s'] . '.');
+      }
+    elseif(isset($_GET['del']))
+      {
+	/* Allow the user to delete schedules that he has stored in his session */
+	if ($_SESSION['saved'][(int)$_GET['del']])
+	  {
+	    /* user owns this schedule ID */
+	    schedule_store_delete($schedule_store, (int)$_GET['del']);
+	    unset($_SESSION['saved'][(int)$_GET['del']]);
+	  }
 
-	}
-	else{
+	header('Location: input.php');
+      }
+    else
+      {
 		$allClasses = new Schedule($_POST['postData']['name']);
 	
 		foreach(sortInputs($_POST) as $class)
@@ -95,14 +110,22 @@ if(!$DEBUG){
 			}
 		}
 		$allClasses->findPossibilities();
+		if (!isset($_SESSION['saved']))
+		  $_SESSION['saved'] = array();
+		$schedule_id = schedule_store_store($schedule_store, $allClasses);
+		if ($schedule_id != NULL)
+		  $_SESSION['saved'][$schedule_id] = $allClasses->getName();
+
+		/*
+		 * writeoutTables() needs to know $schedule_id, so it
+		 * has to be called after we save the schedule. See
+		 * schedule_store_store().
+		 */
 		$allClasses->writeoutTables();
-		if(!isset($_SESSION['saved']))
-			$_SESSION['saved'] = array();
-		array_push ( $_SESSION['saved'], serialize($allClasses));
-	}
-} else {
-
-
+      }
+  }
+else
+  {
 	echo 'DEBUG OUTPUT: 
';
 	foreach(sortInputs($_POST) as $class) {
 		echo 'Class: ' . $class['name'] . '
';
diff --git a/saved_schedules/.keep b/saved_schedules/.keep
new file mode 100644