It is currently October 15th, 2024, 12:58 am

TailFile

Skins that control functions in Windows or Rainmeter
User avatar
jsmorley
Developer
Posts: 22856
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

TailFile

Post by jsmorley »

This skin demonstrates how you can "tail" some number of lines from a text file, like a log file or to-do list or any other file you would like to see some number of the "most recent" items added to the bottom of the file, in reverse order.

This uses a very simple Lua script to read the file. The Lua then sets some number of String measures in the skin that are in a sense acting as "child" measures to the Lua Script measure. The meters then use the values of those String measures to display the lines.

You can simply change the file you want to read in [Variables] and use this as is, or feel free to tear it apart to use the underlying functionality in your skin.

I think it is pretty easy to follow what it is doing, but feel free to ask any questions.
TailFile_2.0.rmskin
1.jpg
Note that you easily could add IfMatch conditions to the String measures, which would for instance allow you to monitor for "error" in a log file, and set a font color different or any other action you like. You could also use RegExpSubstitute to replace text in the line, so for instance you could remove unwanted tabs or spaces, remove the first "xx" characters of the line, or any other change you might want to make with a regular expression search-and-replace.


Important: If you are going to have Unicode characters in the text file you are reading, that file MUST be encoded in UTF-8 with or without BOM. That is the only encoding supported by Lua when reading external Unicode files.
You do not have the required permissions to view the files attached to this post.
User avatar
jsmorley
Developer
Posts: 22856
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: TailFile

Post by jsmorley »

Updated the Lua code to use a vastly more efficient method for reading the last N lines of a large file. The Lua code is based on work by Brian in This post.
PickNick
Posts: 1
Joined: February 23rd, 2022, 9:56 am

Re: TailFile

Post by PickNick »

Hi jsmorley,

thanks for the great skin!
It looks very promising. I was trying to tweak it to my purpose and I noticed during testing that it crashes the whole RainMeter when the source file has no data. Any idea how to prevent this?

Regards,
PickNick
User avatar
ikarus1969
Posts: 591
Joined: February 28th, 2011, 3:20 pm
Location: Vienna, Austria

Re: TailFile

Post by ikarus1969 »

As it is about 2 months since you asked for preventing rainmeter crashing TailFile i had a look at it and made a change to TailFile.lua which just do nothing with the skin but returning -1 if it is a file with 0 bytes.

Technically the seek-function returns nil which i query. As the forum prevents lua-files to be attached to posts you have to copy/paste the content by yourself. Maybe there are some more clever lua-solutions to this but it is just straight forward.

Code: Select all

function Initialize()
      
  fileToRead = SKIN:GetVariable('FileToRead')
  linesToTail = tonumber(SKIN:GetVariable('LinesToTail'))
  childPrefix = SKIN:GetVariable('ChildPrefix')

end

function Update()
   
  local inputFile = assert(io.open(fileToRead, 'r'))
  local singleChar, lineText
  local bytePosition = -1
  local i = 1
  local tooFewLines = false

  repeat
    if inputFile:seek('end', bytePosition - 1) == nil then
      tooFewLines = true;
    else
      singleChar = inputFile:read(1)
      if singleChar == '\n' then
        lineText = inputFile:read(-bytePosition)
        if lineText then
          lineText = string.gsub(lineText, '\r', '')
          SKIN:Bang('!SetOption', childPrefix..i, 'String', string.match(lineText, '^(.-)\n'))
          SKIN:Bang('!UpdateMeasure', childPrefix..i)
          i = i + 1
          bytePosition = bytePosition - 1
        end
      end
      bytePosition = bytePosition - 1
    end
  until (tooFewLines or i > linesToTail)

  io.close(inputFile)

  SKIN:Bang('!UpdateMeter', '*')
  SKIN:Bang('!Redraw')

  return 1
  
end
Crest
Posts: 150
Joined: August 16th, 2013, 12:47 pm

Re: TailFile

Post by Crest »

Is there a way the Lua could be modified to only match lines that begin with a certain string? In my case I'm wanting to match a particular Unicode character (let's say it's ❤) on certain lines.

I tried using if string.match(lineText, "❤") then on line 24 of ikarus' version of the script (line 21 of the original) and alternatively string.find in the same way but it returned the same results, so I'm obviously not looking at this the right way.

Also if this should be its own thread just let me know.
User avatar
SilverAzide
Rainmeter Sage
Posts: 2746
Joined: March 23rd, 2015, 5:26 pm

Re: TailFile

Post by SilverAzide »

Crest wrote: April 30th, 2023, 12:05 pm Is there a way the Lua could be modified to only match lines that begin with a certain string? In my case I'm wanting to match a particular Unicode character (let's say it's ❤) on certain lines.

I tried using if string.match(lineText, "❤") then on line 24 of ikarus' version of the script (line 21 of the original) and alternatively string.find in the same way but it returned the same results, so I'm obviously not looking at this the right way.

Also if this should be its own thread just let me know.
This should definitely not be part of a thread from 2015, but since someone else dug it up from the grave a year ago, then maybe OK... That said, your problem might be explained here: Unicode in Rainmeter, specifically the section "Using Unicode with Lua scripting".
Gadgets Wiki GitHub More Gadgets...
Crest
Posts: 150
Joined: August 16th, 2013, 12:47 pm

Re: TailFile

Post by Crest »

SilverAzide wrote: April 30th, 2023, 3:35 pmYour problem might be explained here: Unicode in Rainmeter, specifically the section "Using Unicode with Lua scripting".
Hmm, I have all my Lua saved with UTF-16 with BOM and the Lua can output Unicode to the skin and debug Log. I originally assumed it was some logic I was overlooking.

Edit: I realize now that lineText is returning all the lines at that point and that string.match(lineText, '^(.-)\n') later is what is outputting just the single line itself, so now I have a bit clearer understanding.

Edit 2: okay, have it working now. Changed from line 24 of ikarus' version like so (where beginSubstring is a variable defined with the desired Unicode character beforehand in the Lua):

Code: Select all

if lineText then
    lineText = string.gsub(lineText, '\r', '')
    lineTextSingle = string.match(lineText, '^(.-)\n')
    if string.match(lineTextSingle, '^'..beginSubstring) then
        SKIN:Bang('!SetOption', childPrefix..i, 'String', lineTextSingle)
        SKIN:Bang('!UpdateMeasure', childPrefix..i)
        i = i + 1
        bytePosition = bytePosition - 1
    end
end
Note: if the first match is on the first line of the text file it won't return it since it expects a newline prior to any match.