If you’re like me, you may download a lot of videos at once and then be forced to ask the question, “Wait, what haven’t I watched yet?” I created lnSponge, a simple TCL script, to keep track of new files for me. It scans a given directory tree and creates symbolic links to all new files in a designated folder. It retains a copy of all the filenames so you can delete the symbolic links after you’re done watching the media and it won’t reappear on the next run.

The script takes in four arguments indicating the path of files to scan, the path to the symbolic link folder, the file to use to hold the database of filenames and the number of trackback days (e.g. ignore all new files older than n days). It produces no output if there are no new files to add, so it can easily be coupled with the Ruby Email Script

Example usage:

./lnsponge.tcl /mnt/orion/tv_shows /mnt/orion/new ~/.tvshows.list 10

Script:

#!/usr/bin/tclsh
#
#  lnSponge - v1.0
#
#    A script designed to create a set of symbolic links
#    in one directory for all new files found in a given
#    directory tree. Created links are kept track of in
#    an index file to prevent creating duplicate links.
#
#    Useful to keep track of new files for users who
#    download a lot of media.
#
#    Sumit Khanna <sumdog@gmail.com>
#      http://penguindreams.org
#
#    Free for noncommercial use
#

namespace eval lnSpongeVars {
  #only consider files that were created in the past n days
  variable mdays

  #path to meida tree
  variable path

  #path to directory with symbolic links
  variable newlinks

  #path to processed file list
  variable plist

  #file descriptor for update process
  variable plistchannel

  #tmp arry for processed files
  variable pfilearray

}

proc printUsage {} {

  puts "lnSponge Usage:"
  puts ""
  puts "lnsponge.tcl <media path> <new links path> <list file> <trackback days>"
  puts ""
  puts "\tmedia path     - path to directories to recursivly search for new media"
  puts "\tnew links path - path to directory that contains links to new media"
  puts "\tlist file      - file which contains list of processed files"
  puts "\ttrackback days - ignore new files that were modified more than n days ago"
  puts ""
}

#reads in command line args into lnSpongeVars namespace
#  make sure $argc ==4 first
proc readCmdArgs {} {
  variable ::lnSpongeVars::mdays
  variable ::lnSpongeVars::path
  variable ::lnSpongeVars::newlinks
  variable ::lnSpongeVars::plist
  global argv

  set path [lindex $argv 0]
  set newlinks [lindex $argv 1]
  set plist [lindex $argv 2]
  set mdays [lindex $argv 3]
}

#ftime = UNIX time/seconds from epoch
#days = number of days
#return true if ftime is < days; else false
proc checkDays { ftime } {

  variable ::lnSpongeVars::mdays

  #seconds per day
  set secdays [ expr $mdays * 24 * 60 * 60 ]
  if { [ expr [ clock seconds ] - $ftime ] <  $secdays  } {
    return 1
  }
  return 0
}


#initial reading of list
# is read in entirety
proc readPList {} {

  variable ::lnSpongeVars::plist
  variable ::lnSpongeVars::pfilearray

  if { [ file exists $plist ] } {
    set fd [open $plist "r"]
    while {[gets $fd line] >= 0} {
        array set pfilearray  [list "$line" "1"]
    }
    close $fd
  }

}

#--functions for appending to processed file list
proc openPFile {} {
  variable ::lnSpongeVars::plist
  variable ::lnSpongeVars::plistchannel

  set plistchannel [open $plist "a"]
}

proc addPFile { name } {
  variable ::lnSpongeVars::plistchannel
  puts $plistchannel $name
}

proc closePFile {} {
  variable ::lnSpongeVars::plistchannel
  close $plistchannel
}
#--

#recursive function for scanning
# all directories from a given path
# and creating links for those files
# not on processed list
#$rpath = base of tree to scan
proc probeDirs {rpath} {

  variable ::lnSpongeVars::pfilearray
  variable ::lnSpongeVars::newlinks

  if {[ file isdirectory $rpath ]} {
    set i [glob -nocomplain -directory $rpath *]
    foreach sub $i {
      probeDirs $sub
    }
  } else {
    file stat $rpath finfo
    set ftime [ lindex [ array get finfo "ctime" ] 1 ]
    set btime [ checkDays $ftime ]
    #if it was created in the past n days
    if { $btime == 1 } {
      #if it's not all ready in processed list
      set basename [file tail $rpath]
      if {![info exists pfilearray($basename)]} {
        if { [ file exists "$newlinks/$basename" ] } {
          puts "File Exists (ignoring): $basename"
        } else {
          puts "New Link: $basename"
          file link -symbolic "$newlinks/$basename" $rpath
        }
        addPFile [file tail $rpath]
      }
    }
  }
}

#main entry point
# functions must be called in this order
if { $argc != 4 } {
  printUsage
  exit 1
} else {
  readCmdArgs
  readPList
  openPFile
  probeDirs $::lnSpongeVars::path
  closePFile
}