srecord noeq FollowTickerFile(File tickerFile) is AutoCloseable { settable int historyEntriesToGrab; settable int interval = 250; settable ISleeper sleeper = defaultSleeper(); // internal, may change TailFile tailFile; event historicLine(S line); event liveLine(S line); gettable volatile long liveTimestamp; gettable volatile S lastLiveLine; settable transient bool started; event fireStarted; settable int maxLineLengthForTickerEntry = 200; settable Q q = startQ(); bool isLive() { ret liveTimestamp != 0; } void start { q.add(r { if (started()) ret; started(true); fireStarted(); onLiveLine(line -> { lastLiveLine = line; liveTimestamp = now(); }); long fileSize = l(tickerFile); long start = max(0, fileSize-maxLineLengthForTickerEntry*historyEntriesToGrab); if (historyEntriesToGrab > 0) { S text = loadTextFilePart(tickerFile, start, fileSize); LS lastTickerLines = takeLast(historyEntriesToGrab, lines(text)); print("Have historic lines: " + l(lastTickerLines) + "/" + historyEntriesToGrab); for (line : lastTickerLines) historicLine(line); } makeTailFile(fileSize); }); } void makeTailFile(long fileSize) { started(true); print("Following " + tickerFile + " (" + n2(l(tickerFile)) + " bytes) from position " + n2(fileSize)); tailFile = tailFileLinewiseFromPosition(tickerFile, interval, fileSize, l1 liveLine, sleeper); } close { started(false); if (tailFile != null) { print("Done " + tailFile); dispose tailFile; } } void switchToFile(File newFile) { if (eq(tickerFile, newFile)) ret; q.add(r { if (eq(tickerFile, newFile)) ret; print("Switching to ticker file: " + newFile); if (started()) { close(); tickerFile = newFile; started(true); makeTailFile(0); } else tickerFile = newFile; }); } selfType onLine(IVF1 l) { onHistoricLine(l); ret onLiveLine(l); } toString { ret commaCombine( "FollowTickerFile", stringIf(started(), "started"), !isLive() ? null : "Last live line at " + formatLocalDateWithSeconds(liveTimestamp), tickerFile ); } }