2

I have a camera based security system that uploads all its images via FTP to a folder in one of my hosting accounts. (FYI, I use it to watch my cats when I'm traveling). So I created a PHP file to simply display all the images along with their names and timestamp. Unfortunately the camera software creates separate folders for each camera, and another for each day. So I used the RecursiveDirectoryIterator class, which makes it easy to build arrays containing just the image files, which makes display pretty easy. But now I realize it would be much more useful if it displayed all the files in order of timestamp.

In the below, you can see where I've built up arrays for all the timestamps, filenames, and fullpaths so I can display in the for() loop at the bottom. I think what I need to do now is create an array of $indexes that are based on a sort of the timestamps. then in the display loop, I could substitute $indexes[$i] wherever the simple $i index is used now. But I'm not sure how to do that. Thanks for any help.

Edit: I realize I'll have to add a line saving the rawtimestamp integers in an array, for whatever way I sort it.

<?php
$dir_path = "private/cats/";
$extensions_array = array('jpg','png','jpeg','JPG','PNG','JPEG');
$total = 0;

$objects = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir_path),

RecursiveIteratorIterator::LEAVES_ONLY);
  foreach($objects as $filepath => $object)
  {
   $fileinfo = pathinfo($filepath);
   // skip if no extension, or extension not in my allowed list
   if (!isset($fileinfo['extension'])) continue;
   if (!in_array($fileinfo['extension'], $extensions_array)) continue;

   $timestamps[$total] = date("F d Y H:i:s", filemtime($filepath));
   $filenames[$total] = $fileinfo['filename'] . "." .  $fileinfo['extension'];
   $filepaths[$total] = $filepath;

   $total++;

  }

echo "<h1>$total Image Files</h1>";
for ($i=0; $i < $total; $i++)
  {
   echo "<br><hr><h1>$filenames[$i] &nbsp;&nbsp; $timestamps[$i] <br></h1>";
   echo "<img src='$filepaths[$i]' style='width:100%;height:auto;'><br>";
  }
?>
  • There go the "down vote" police again. Idiots. They think they are creating a repository for "good questions" and good answers". They should do some "googling" and they'd see how many people get foul treatment on these boards when they come looking for help. Maybe someday they'll realize that what they are really doing is giving StackExchange programming boards a progressively worse reputation. As for me, I post, always learn something, and always help others. When I'mt banned for doing so i move on. They should remember that the only 'dumb' question is the one you don't ask. – Randy Nov 18 '18 at 23:28
0

Keeping 3 different arrays in-synch while sorting will be a tough job.

I would instead use a multi-dimensional associative array OR a basic data structure type class/object to hold all 3 data items about each file as one unit. Since I'm lazy and the question is likely to get locked, associative array:

  foreach($objects as $filepath => $object)
  {
   $fileinfo = pathinfo($filepath);
   // skip if no extension, or extension not in my allowed list
   if (!isset($fileinfo['extension'])) continue;
   if (!in_array($fileinfo['extension'], $extensions_array)) continue;
   /*
   $timestamps[$total] = date("F d Y H:i:s", filemtime($filepath));
   $filenames[$total] = $fileinfo['filename'] . "." .  $fileinfo['extension'];
   $filepaths[$total] = $filepath;
   */
   $files[]=array("timestamp"=>filemtime($filepath),
                  "filename"=>$fileinfo['filename'].".".$fileinfo['extension'],
                  "filepath"=>$filepath);

  }

Now you can write your own sorting function based on the timestamp (stored in unix time still, format on display but pure int is easier to sort), and call it with usort(). Once it is sorted, just loop through it, ie $files[42]['timestamp'] etc

  • Right... I realized that I need to be saving the raw timestamps too. I confess I have found the "MANual" description of associative arrays very confusing, and I never found a clear explanation of how the => operator worked. hence, my ignorance there made it hard to understand the usort() call. In fact, your example has shown me (for the first time) that the associations can be variables (they are all shown as string literals in the MANual). So thanks for teaching me that! Side rant: I think these forums are too harsh on people trying to learn basics. I already got a down vote, no explanation. – Randy Nov 18 '18 at 19:29
  • Incidentally, I found the PHP array_multisort() quite handy if I did want to continue keeping multiple arrays in sync during the sort. That function ultimately did what my "brute force" solution did in fewer lines of code, but it was still a worthwhile exercise to roll my own. I also tried something like you suggested but made the mistake of letting my timestamp integers be my "keys". That was a mistake because files with the same time stamps get lost. But the straight up method you are suggesting will work fine. – Randy Nov 23 '18 at 20:01
  • One problem I'm having with your method is displaying the resulting files in my PHP echo statements. Part of the confusion and problem is that what i echo must be in quotes, and now that I'm using identifiers "filename", "filepath", etc that are also in quotes, my syntax is usually wrong. If you were writing a loop to walk the $files array in your example with a foreach() construct, how would you echo (for example) one of the filenames? – Randy Nov 23 '18 at 20:05
  • @Randy You can use single quotes $var['index'] or doubles $var["index"] interchangeably - just use the opposite of what you are using in your print() calls – ivanivan Nov 23 '18 at 20:06
  • 1
    @Randy - not sure I'm getting the issue. Should need one kind to contain string to be printed, one kind of id the named indexes. Perhaps post a new question regarding quoting? – ivanivan Nov 23 '18 at 20:13
0

OK, I'll grant that there are a lot of PHP constructs I don't understand, and I have a tendency to use simpler old school 'C' style methods. But the first goal is to get code to work as desired, and then improve it. With that in mind, a further complication I discovered is that for whatever reason, my camera security software occasionally uploads duplicate files (same content, name, and timestamp) to the different directories. So here was my overall solution to displaying the images in order, while eliminating duplicates. That last part isn't perfect yet, because so far I'm just assuming duplicate time stamps means duplicate files. But I've shown a way of keeping multiple files with dup timestamps in the comments.

So first, I modified the original block of code to also record $rawtimestamps, along with the file names, formatted time stamps, and full paths. As someone pointed out already in another answer, the raw timestamp integers will certainly be easier to sort.

    <?php
$dir_path = "private/cats/";
$extensions_array = array('jpg','png','jpeg','JPG','PNG','JPEG');
$total = 0;

$objects = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir_path), RecursiveIteratorIterator::LEAVES_ONLY);
foreach($objects as $filepath => $object)
  {
   $fileinfo = pathinfo($filepath);
   // skip if no extension, or extension not in my allowed list
   if (!isset($fileinfo['extension'])) continue;
   if (!in_array($fileinfo['extension'], $extensions_array)) continue;

   $rawTimestamps[$total] = filemtime($filepath); // <- new addition.
   $timestamps[$total] = date("F d Y H:i:s", filemtime($filepath));
   $filenames[$total] = $fileinfo['filename'] . "." .  $fileinfo['extension'];
   $filepaths[$total] = $filepath;

   $total++;

  }

Then I concocted this brute force loop to create a new array called $indexes. It will serve as a mapping table, holding indexes to progressively higher timestamps in the original arrays.

 $max = max($rawTimestamps);
 $newtotal=0;

 for ($i=0; $i < $total; $i++)
  {
   $lowest = min($rawTimestamps);
   if ($lowest > $max) break;  // stop when no more.

   for ($j= 0; $j < $total; $j++)
     {
      if ($rawTimestamps[$j] != $lowest) continue;
      $indexes[$newtotal] = $j;    // keep index. overwrite duplicates
      $rawTimestamps[$j] = $max+1; // ensure never counted again.
     }
  $newtotal++;   // ** this is the new real total

  }

What is happening here is that I've used the max() function to record the highest value in the $rawtimestamp array. Then in the outer loop I record the $lowest value in the raw timestamp array, exiting when lowest number found is greater than the max value originally found. This will jmake sense in a moment. In the inner loop I look for that 'lowest' timestamp and when i find it, I place the index ($j) to where it was found in my new $indexes array. I then replace the raw time stamp with a number greater than the originally found max value. That way it won't be found by the inner loop again. This destroys $rawtimeatamps as the process continues, but I won't need them anymore. When the lowest timestamp found is equal to the originally determined max value, that means all the $rawtimestamps have been replaced, and the loop exists with a break;

Note that the $newTotal is counted and used to reference where the subsequent $indexes are stored. If duplicate timestamps are found, they get overwritten, because the $newtotal is not incremented until the inner loop exits. If I wanted to keep multiple files with the same timestamp, comment out where $newtotal is incremented, and change this line so the total increments each time a value is placed in $indexes...

$indexes[$newtotal++] = $j;    // keep index and do NOT overwrite duplicates

So now all I needed to do was modify my display loop. Instead of indexing my filenames directly, I use my $indexes "map" to "look up" the file data according to progressively higher timestamps, as I display them.

  echo "<h1>$newtotal Image Files</h1>";
  for ($i=0; $i < $newtotal; $i++)
    {
    $ix= $indexes[$i]; // get index to progressively older files 
    echo "<br><hr><h1>$filenames[$ix] &nbsp;&nbsp; $timestamps[$ix] <br></h1>";
    echo "<img src='$filepaths[$ix]' style='width:100%;height:auto;'><br>";
    }

Again, I'm sure this is not an eloquent solution, but it works. If anyone wishes to offer a better way to do what I did here, I'm happy to learn.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.