saintamh

Commit 943

User picture
  • Author: saintamh
  • 2010-04-28 15:26 (almost 4 years ago)

scheduler WIP - won't run just as is

Files Affected

 
Show contents
Show contents
942943
2
// Herve Saint-Amand
2
// Herve Saint-Amand
3
// Edinburgh
3
// Edinburgh
4
 
4
 
5
//---------------------------------------------------------------------------------------------------------------------------------
5
//-----------------------------------------------------------------------------
6
 
6
 
7
import java.io.{
7
import java.io.File
8
  BufferedReader,
8
import java.util.Calendar
9
  InputStream,
 
 
10
  InputStreamReader
 
 
11
}
 
 
12
 
9
 
13
import java.util.{
10
//-----------------------------------------------------------------------------
14
  Calendar
 
 
15
}
 
 
16
 
11
 
17
//---------------------------------------------------------------------------------------------------------------------------------
12
object Scheduler {
18
 
13
 
19
class Timer (amount: Int, calField: Int) {
14
  // TODO search for and auto-locate this from a choice of system/user dirs
20
  def runNow (now: Calendar, lastRun: Calendar) = {
15
  val LAST_RUNTIME_FILE = new File (
21
    if (lastRun == null) {
16
    "C:\\Documents and Settings\\"
22
      true
17
    + System.getProperties.get("user.name")
23
    } else {
18
    + "\\Application Data\\task-scheduler-run-times.dat"
24
      val nextRun = lastRun.clone().asInstanceOf[Calendar]
19
  )
25
      nextRun.add (calField, amount)
 
 
26
      now after nextRun
 
 
27
    }
 
 
28
  }
 
 
29
}
 
 
30
 
20
 
31
object SELF_TIMED_TASK extends Timer (1, Calendar.MINUTE)
21
  implicit def thread (func: () => Unit) = 
 
 
22
    new Thread { override def run () = func() }
32
 
23
 
33
//---------------------------------------------------------------------------------------------------------------------------------
24
  //---------------------------------------------------------------------------
 
 
25
  // main
34
 
26
 
35
sealed case class TaskStatus (rank: Int, colorName: String) {
27
  def main (args: Array[String]) {
36
  def worseThan (that: TaskStatus) =
 
 
37
    rank < that.rank
 
 
38
}
 
 
39
 
28
 
40
case object ERROR   extends TaskStatus (0, "red")
29
    val tasks = Array[Task]() ++ TasksConfigFile.load
41
case object WARNING extends TaskStatus (1, "orange")
30
    LastRunTimeDB.load (tasks, LAST_RUNTIME_FILE)
42
case object RUNNING extends TaskStatus (2, "gray")
 
 
43
case object IDLE    extends TaskStatus (3, "green")
 
 
44
 
31
 
45
//---------------------------------------------------------------------------------------------------------------------------------
32
    for (t <- tasks) {
46
 
33
      println (t.timer.nextRun (
47
case class Task (
34
        Calendar.getInstance,
48
  val label: String,
35
        t.lastRunAttempt,
49
  val cmdLine: Array[String],
36
        t.lastRunSuccess
50
  val timer: Timer
37
      ).getTime)
51
) {
38
    }
52
  var lastRun: Calendar = null
 
 
53
  var status: TaskStatus = IDLE
 
 
54
  val lastOutput: StringBuffer = new StringBuffer()
 
 
55
 
 
 
56
  def run (now: Calendar, statusUpdated: ()=>Unit) {
 
 
57
    status = RUNNING
 
 
58
    statusUpdated ()
 
 
59
    status = pickStatusFromChildRetVal (exec())
 
 
60
    statusUpdated()
 
 
61
    lastRun = now
 
 
62
  }
39
  }
63
 
40
 
64
  private def exec () = {
41
  def bork (tasks: Array[Task]) {
65
    println (cmdLine.mkString(" "))
42
    val ui = new UI (tasks)
66
    val child = Runtime.getRuntime.exec (cmdLine)
43
    ui.updateTaskIcons()
67
    val childOut = child.getInputStream
 
 
68
    val childErr = child.getErrorStream
 
 
69
 
44
 
70
    // FIXME we 1st wait for the child to exit, then read its output -- this will buffer the whole output to memory, what if
45
    def preTaskRun (task: Task) {
71
    // there's loads?
46
      // TODO wait 1s before changing the icon, so that tasks that return
72
    child.waitFor()
47
      // (almost) immediately don't make the icon flicker? Or maybe it's good
73
 
48
      // that it flickers, actually? (shows it's alive). This would require a
74
    lastOutput.setLength (0)
49
      // 3rd thread, but shouldn't be hard.
75
    def milk (in: InputStream) {
50
      task.status = RUNNING
76
      val reader = new BufferedReader (new InputStreamReader (in))
51
      ui.updateTaskIcons()
77
      var line = reader.readLine
 
 
78
      while (line != null) {
 
 
79
        println (line)
 
 
80
        lastOutput.append (line + "\n")
 
 
81
        line = reader.readLine
 
 
82
      }
 
 
83
    }
52
    }
84
    milk (childOut)
 
 
85
    milk (childErr)
 
 
86
    
 
 
87
    child.exitValue
 
 
88
  }
 
 
89
 
53
 
90
  private def pickStatusFromChildRetVal (retVal: Int) =
54
    def postTaskRun (
91
    if (retVal < 0)
55
      task: Task,
92
      WARNING
56
      now: Calendar,
93
    else if (retVal > 0)
57
      newStatus: TaskStatus,
94
      ERROR
58
      output: String
95
    else
59
    ) {
96
      IDLE
60
      task.status = newStatus
97
 
61
      task.lastRunAttempt = Some(now)
98
  def hasRun =
62
      if (newStatus == SUCCESSFUL) {
99
    lastRun != null
63
        task.lastRunSuccess = Some(now)
100
}
64
        LastRunTimeDB.save (tasks, LAST_RUNTIME_FILE)
101
 
65
      // } else if (taskIsOverdue(task,now)) {
102
//---------------------------------------------------------------------------------------------------------------------------------
66
      //   task.status = OVERDUE
103
 
 
 
104
object Scheduler {
 
 
105
 
 
 
106
  //-------------------------------------------------------------------------------------------------------------------------------
 
 
107
  // implicit conversions
 
 
108
 
 
 
109
  implicit def thread (func: () => Unit) = 
 
 
110
    new Thread { override def run () = func() }
 
 
111
 
 
 
112
  //-------------------------------------------------------------------------------------------------------------------------------
 
 
113
  // Tasks list
 
 
114
 
 
 
115
  val tasks = Array (
 
 
116
    Task (
 
 
117
      "iTunes Library Backup",
 
 
118
      Array ("perl", "tasks/itunes.pl"),
 
 
119
      SELF_TIMED_TASK
 
 
120
    ),
 
 
121
    Task (
 
 
122
      "Oscar data dump",
 
 
123
      Array ("perl", "tasks/oscar.pl"),
 
 
124
      SELF_TIMED_TASK
 
 
125
    ),
 
 
126
    Task (
 
 
127
      "Bogus",
 
 
128
      Array ("perl", "-e", "sleep 2; print qq!Je suis un chauffeur de van\\n!; exit -1"),
 
 
129
      new Timer (10, Calendar.SECOND)
 
 
130
    )
 
 
131
  )
 
 
132
 
 
 
133
  //-------------------------------------------------------------------------------------------------------------------------------
 
 
134
  // main
 
 
135
 
 
 
136
  def mainLoop (updateUI: ()=>Unit) {
 
 
137
    while (true) {
 
 
138
      val now = Calendar.getInstance
 
 
139
      val tasksToRun = for (task <- tasks; if task.timer.runNow(now,task.lastRun)) yield task
 
 
140
 
 
 
141
      updateUI()
 
 
142
      if (tasksToRun.length > 0) {
 
 
143
        for (task <- tasksToRun)
 
 
144
          task.run (now, updateUI)
 
 
145
      } else {
 
 
146
        sleep (60)
 
 
147
      }
67
      }
 
 
68
      ui.updateTaskIcons()
148
    }
69
    }
149
  }
 
 
150
 
70
 
151
  def main (args: Array[String]) {
 
 
152
    val ui = new UI (tasks)
 
 
153
 
 
 
154
    Runtime.getRuntime.addShutdownHook (() => {
71
    Runtime.getRuntime.addShutdownHook (() => {
155
      // Always call this when exiting, no matter how we exit (crash or otherwise), as if we don't explicitly remove the systray
72
      // Always call this when exiting, no matter how we exit (crash or
156
      // icon it stays there until you mouseover it, giving the false impression that the system is still running
73
      // otherwise), as if we don't explicitly remove the systray icon it stays
 
 
74
      // there until you mouseover it, giving the false impression that the
 
 
75
      // system is still running
157
      ui.exit()
76
      ui.exit()
158
    })
77
    })
159
 
78
 
160
    // Fork off a thread for task handling, then run the UI in the main thread.
79
    // Fork off a thread for task handling, then run the UI in the main thread.
161
    //
80
    //
162
    // Instinctively I would have preferred having it the other way around (the UI in a separate thread), but the initialization
81
    // Instinctively I would have preferred having it the other way around (the
163
    // (above) and the main loop must be run in the same thread (otherwise SWT throws a runtime exception), and so I thought this
82
    // UI in a separate thread), but the initialization (above) and the main
164
    // becomes the best way to handle it: the main thread becomes SWT's event-handling loop, and a separate thread runs the tasks
83
    // loop must be run in the same thread (otherwise SWT throws a runtime
165
    new Thread () {
84
    // exception), and so I thought this becomes the best way to handle it: the
166
      override def run =
85
    // main thread becomes SWT's event-handling loop, and a separate thread
167
        mainLoop (()=> ui.updateTaskIcons)
86
    // runs the tasks
168
    }.start()
87
    new Thread (() => {
 
 
88
      mainLoop (tasks, preTaskRun, postTaskRun)
 
 
89
    }).start()
169
    ui.mainLoop()
90
    ui.mainLoop()
170
  }
91
  }
171
 
92
 
172
  //-------------------------------------------------------------------------------------------------------------------------------
93
  def mainLoop (
173
  // utils
94
    tasks: Array[Task],
174
 
95
    preTaskRun: (Task)=>Unit,
175
  def sleep (seconds: Int) {
96
    postTaskRun: (Task,Calendar,TaskStatus,String)=>Unit
176
    try {
97
  ) {
177
      Thread.sleep (seconds * 1000)
98
    while (true) {
178
    } catch {
99
      val now = Calendar.getInstance
179
      case ex: InterruptedException =>
100
      for {
180
        ex.printStackTrace
101
        task <- tasks
 
 
102
        // if taskIsDueToRun (task, now)
 
 
103
      } {
 
 
104
        preTaskRun (task)
 
 
105
        val output = new StringBuffer
 
 
106
        val exitValue = ProcessUtils.exec (task.cmdLine, output)
 
 
107
        val status = TaskStatus.parseChildExitValue (exitValue)
 
 
108
        postTaskRun (task, now, status, output.toString)
 
 
109
      }
 
 
110
      TimeUtils.sleep (60) // TODO pick each task's next run time
181
    }
111
    }
182
  }
112
  }
183
 
113
 
184
  //-------------------------------------------------------------------------------------------------------------------------------
114
  //---------------------------------------------------------------------------
185
 
115
 
186
}
116
}
187
 
117
 
188
//---------------------------------------------------------------------------------------------------------------------------------
118
//-----------------------------------------------------------------------------
Show contents
Show contents
942943
4
 
4
 
5
//---------------------------------------------------------------------------------------------------------------------------------
5
//---------------------------------------------------------------------------------------------------------------------------------
6
 
6
 
7
import java.io.{
7
import java.io.File
8
  File
 
 
9
}
 
 
10
 
8
 
11
import org.eclipse.swt.{
9
import org.eclipse.swt.SWT
12
  SWT
10
import org.eclipse.swt.graphics.Image
13
}
11
import org.eclipse.swt.layout.FillLayout
14
 
12
 
15
import org.eclipse.swt.graphics.{
 
 
16
  Image
 
 
17
}
 
 
18
 
 
 
19
import org.eclipse.swt.layout.{
 
 
20
  FillLayout
 
 
21
}
 
 
22
 
 
 
23
import org.eclipse.swt.widgets.{
13
import org.eclipse.swt.widgets.{
24
  Dialog,
 
 
25
  Display,
14
  Display,
26
  Event,
15
  Event,
27
  Listener,
16
  Listener,
...
 
...
 
48
  //-------------------------------------------------------------------------------------------------------------------------------
37
  //-------------------------------------------------------------------------------------------------------------------------------
49
  // init GUI layout components
38
  // init GUI layout components
50
 
39
 
51
  // basic components
40
  // basic SWT components
52
  val display = new Display()
41
  val display = new Display()
53
  val shell = new Shell (display)
42
  val shell = new Shell (display)
54
  val taskOutputDialog = new TaskOutputDialog ()
 
 
55
 
43
 
56
  // tray icon
44
  // tray icon
57
  val tray = display.getSystemTray()
45
  val tray = display.getSystemTray()
...
 
...
 
73
      taskItem.setMenu (new Menu (menuShell, SWT.DROP_DOWN))
61
      taskItem.setMenu (new Menu (menuShell, SWT.DROP_DOWN))
74
 
62
 
75
      // Submenu item to open task output dialog
63
      // Submenu item to open task output dialog
76
      if (task.hasRun) {
64
      if (task.lastOutput != null) {
77
        val taskOutputItem = new MenuItem (taskItem.getMenu, SWT.NONE)
65
        val taskOutputItem = new MenuItem (taskItem.getMenu, SWT.NONE)
78
        taskOutputItem.setText ("View Output From Last Run")
66
        taskOutputItem.setText ("View Output From Last Run")
79
        taskOutputItem.addListener (SWT.Selection, (evt:Event)=> {
67
        taskOutputItem.addListener (SWT.Selection, (evt:Event)=> {
80
          taskOutputDialog.show (task.lastOutput.toString)
68
          (new TaskOutputDialog ()).show (task.lastOutput.toString)
81
        })
69
        })
82
      }
70
      }
83
 
71
 
84
      // Bogus, disabled submenu item indicating last run time
72
      // Bogus, disabled submenu items indicating last run times
85
      val lastRunItem = new MenuItem (taskItem.getMenu, SWT.NONE)
73
      for {
86
      lastRunItem.setEnabled (false)
74
        (label, time) <- List (
87
      lastRunItem.setText ("Last Run: " + (if (task.hasRun) task.lastRun.getTime else "Never"))
75
          ("Last Run Attempt", task.lastRunAttempt),
 
 
76
          ("Last Run Success", task.lastRunSuccess)
 
 
77
        )
 
 
78
      } {
 
 
79
        val lastRunItem = new MenuItem (taskItem.getMenu, SWT.NONE)
 
 
80
        lastRunItem.setEnabled (false)
 
 
81
        lastRunItem.setText (label + ": " + (time match {
 
 
82
          case Some(t) => t.getTime
 
 
83
          case None => "Never"
 
 
84
        }))
 
 
85
      }
88
    }
86
    }
89
 
87
 
90
    new MenuItem (menu, SWT.SEPARATOR)
88
    new MenuItem (menu, SWT.SEPARATOR)
...
 
...
 
95
  // status icons
93
  // status icons
96
  val ICON_EXT = ".ico"
94
  val ICON_EXT = ".ico"
97
  val ICON_DIR = new File ("icons", "slashdot")
95
  val ICON_DIR = new File ("icons", "slashdot")
98
  val ICONS = Map.empty[String,Image] ++ Array ("green", "gray", "orange", "red").map {
96
  val ICONS = Map.empty[String,Image] ++ Array ("green", "gray", "orange", "red", "blue").map {
99
    colorName => {
97
    colorName => {
100
      val iconFile = new File (ICON_DIR, colorName + ICON_EXT)
98
      val iconFile = new File (ICON_DIR, colorName + ICON_EXT)
101
      val image = new Image (display, getClass.getResourceAsStream (iconFile.getPath))
99
      val image = new Image (display, getClass.getResourceAsStream (iconFile.getPath))
Show contents
942943
37
};
37
};
38
 
38
 
39
#------------------------------------------------------------------------------
39
#------------------------------------------------------------------------------
 
 
40
# constants
 
 
41
 
 
 
42
use constant {
 
 
43
    ONLINE_CHECK_URL => 'http://www.google.com/',
 
 
44
};
 
 
45
 
 
 
46
#------------------------------------------------------------------------------
40
# Generic backup procedures
47
# Generic backup procedures
41
 
48
 
42
sub backup_net_file_to_constant_local_path {
49
sub backup_net_file_to_constant_local_path {
...
 
...
 
122
    # Fetch it
129
    # Fetch it
123
    &log ('  ' . $req->uri);
130
    &log ('  ' . $req->uri);
124
    my $res = $lwp->request ($req);
131
    my $res = $lwp->request ($req);
125
    die '    ' . $res->status_line
 
 
126
        unless ($res->is_success);
 
 
127
 
132
 
 
 
133
    # Handle failures -- is it because we're not connected to the Internet, or
 
 
134
    # the link is bad?
 
 
135
    unless ($res->is_success) {
 
 
136
        if ($lwp->head(ONLINE_CHECK_URL)->is_success) {
 
 
137
            die '    ' . $res->status_line;
 
 
138
        } else {
 
 
139
            print "No Internet connection\n";
 
 
140
            exit -1;
 
 
141
        }
 
 
142
    }
 
 
143
 
128
    return $res->decoded_content;
144
    return $res->decoded_content;
129
}
145
}
130
 
146
 
Show contents