timefixer 2023.12.27.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- timefixer-2023.12.27.1/PKG-INFO +26 -0
- timefixer-2023.12.27.1/README.md +9 -0
- timefixer-2023.12.27.1/pyproject.toml +38 -0
- timefixer-2023.12.27.1/setup.cfg +4 -0
- timefixer-2023.12.27.1/timefixer.egg-info/PKG-INFO +26 -0
- timefixer-2023.12.27.1/timefixer.egg-info/SOURCES.txt +9 -0
- timefixer-2023.12.27.1/timefixer.egg-info/dependency_links.txt +1 -0
- timefixer-2023.12.27.1/timefixer.egg-info/entry_points.txt +5 -0
- timefixer-2023.12.27.1/timefixer.egg-info/requires.txt +1 -0
- timefixer-2023.12.27.1/timefixer.egg-info/top_level.txt +1 -0
- timefixer-2023.12.27.1/timefixer.py +183 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: timefixer
|
|
3
|
+
Version: 2023.12.27.1
|
|
4
|
+
Summary: A service to fix files with timestamps in the future.
|
|
5
|
+
Author-email: Ernesto Alfonso <erjoalgo@gmail.com>
|
|
6
|
+
Maintainer-email: Ernesto Alfonso <erjoalgo@gmail.com>
|
|
7
|
+
Project-URL: Homepage, https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer
|
|
8
|
+
Project-URL: Documentation, https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer/README.md
|
|
9
|
+
Project-URL: Repository, https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/erjoalgo/dotfiles/issues
|
|
11
|
+
Keywords: timefixer,timestamp,modified,future
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Requires-Python: >=3.7
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: watchdog
|
|
17
|
+
|
|
18
|
+
This service helps to fix newly created or modified files with timestamps in the future.
|
|
19
|
+
This can happen occassionally when downloading files with incorrect timestamps based on different time zones.
|
|
20
|
+
Fixing such cases makes it possible to reliably fetch the latest modified file from a list of directories, which helps my productivity.
|
|
21
|
+
|
|
22
|
+
To install:
|
|
23
|
+
|
|
24
|
+
`$ pip install timefixer`
|
|
25
|
+
|
|
26
|
+
`$ timefixer --install-systemd`
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
This service helps to fix newly created or modified files with timestamps in the future.
|
|
2
|
+
This can happen occassionally when downloading files with incorrect timestamps based on different time zones.
|
|
3
|
+
Fixing such cases makes it possible to reliably fetch the latest modified file from a list of directories, which helps my productivity.
|
|
4
|
+
|
|
5
|
+
To install:
|
|
6
|
+
|
|
7
|
+
`$ pip install timefixer`
|
|
8
|
+
|
|
9
|
+
`$ timefixer --install-systemd`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "timefixer"
|
|
7
|
+
version = "2023.12.27.1"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"watchdog",
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.7"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Ernesto Alfonso", email = "erjoalgo@gmail.com"},
|
|
14
|
+
]
|
|
15
|
+
maintainers = [
|
|
16
|
+
{name = "Ernesto Alfonso", email = "erjoalgo@gmail.com"},
|
|
17
|
+
]
|
|
18
|
+
description = "A service to fix files with timestamps in the future."
|
|
19
|
+
readme = "README.md"
|
|
20
|
+
license = {file = "LICENSE"}
|
|
21
|
+
keywords = ["timefixer", "timestamp", "modified", "future"]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Programming Language :: Python"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer"
|
|
29
|
+
Documentation = "https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer/README.md"
|
|
30
|
+
Repository = "https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer"
|
|
31
|
+
"Bug Tracker" = "https://github.com/erjoalgo/dotfiles/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
timefixer = "timefixer.timefixer:main"
|
|
35
|
+
|
|
36
|
+
[project.entry-points."timefixer.timefixer"]
|
|
37
|
+
timefixer = "timefixer.timefixer:main"
|
|
38
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: timefixer
|
|
3
|
+
Version: 2023.12.27.1
|
|
4
|
+
Summary: A service to fix files with timestamps in the future.
|
|
5
|
+
Author-email: Ernesto Alfonso <erjoalgo@gmail.com>
|
|
6
|
+
Maintainer-email: Ernesto Alfonso <erjoalgo@gmail.com>
|
|
7
|
+
Project-URL: Homepage, https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer
|
|
8
|
+
Project-URL: Documentation, https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer/README.md
|
|
9
|
+
Project-URL: Repository, https://github.com/erjoalgo/dotfiles/tree/master/bin/timefixer
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/erjoalgo/dotfiles/issues
|
|
11
|
+
Keywords: timefixer,timestamp,modified,future
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Requires-Python: >=3.7
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: watchdog
|
|
17
|
+
|
|
18
|
+
This service helps to fix newly created or modified files with timestamps in the future.
|
|
19
|
+
This can happen occassionally when downloading files with incorrect timestamps based on different time zones.
|
|
20
|
+
Fixing such cases makes it possible to reliably fetch the latest modified file from a list of directories, which helps my productivity.
|
|
21
|
+
|
|
22
|
+
To install:
|
|
23
|
+
|
|
24
|
+
`$ pip install timefixer`
|
|
25
|
+
|
|
26
|
+
`$ timefixer --install-systemd`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
watchdog
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
timefixer
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Observe a directory and new fix files with timestamps in the future.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
import subprocess
|
|
14
|
+
|
|
15
|
+
logger = logging
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import watchdog.events
|
|
19
|
+
import watchdog.observers
|
|
20
|
+
except ImportError as ex:
|
|
21
|
+
logger.warning("failed to import watchdog: %s", ex)
|
|
22
|
+
watchdog = None
|
|
23
|
+
|
|
24
|
+
class FsObserver():
|
|
25
|
+
"""Invoke a callback on file changes in a directory."""
|
|
26
|
+
def __init__(self, directory, on_change):
|
|
27
|
+
self.directory = directory
|
|
28
|
+
self.observer = watchdog.observers.Observer()
|
|
29
|
+
self.on_change = on_change
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def __handler__(on_change):
|
|
33
|
+
class Handler(watchdog.events.FileSystemEventHandler):
|
|
34
|
+
"""Invoke the user-provided callback."""
|
|
35
|
+
def __init__(self, on_change):
|
|
36
|
+
self.on_change = on_change
|
|
37
|
+
|
|
38
|
+
def on_created(self, event):
|
|
39
|
+
logger.debug(f'File created: {event.src_path}')
|
|
40
|
+
self.on_change("created", event.src_path, event)
|
|
41
|
+
|
|
42
|
+
def on_modified(self, event):
|
|
43
|
+
logger.debug(f'File modified: {event.src_path}')
|
|
44
|
+
self.on_change("modified", event.src_path, event)
|
|
45
|
+
|
|
46
|
+
def on_moved(self, event):
|
|
47
|
+
logger.debug(f'File moved: {event.src_path} => {event.dest_path}')
|
|
48
|
+
self.on_change("moved", event.dest_path, event)
|
|
49
|
+
|
|
50
|
+
return Handler(on_change)
|
|
51
|
+
|
|
52
|
+
def start(self):
|
|
53
|
+
"""Start the FS Observer to listen and react to filesystem events."""
|
|
54
|
+
event_handler = self.__handler__(self.on_change)
|
|
55
|
+
self.observer.schedule(event_handler, self.directory, recursive=True)
|
|
56
|
+
self.observer.start()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def join(self):
|
|
60
|
+
"""Blockingly join the observer thread."""
|
|
61
|
+
self.observer.join()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TimestampFixer:
|
|
65
|
+
"""Fix created or modified files in the given directories with timestamps in the future."""
|
|
66
|
+
def __init__(self, dirs):
|
|
67
|
+
self.observers = [FsObserver(directory,
|
|
68
|
+
self.onchange)
|
|
69
|
+
for directory in dirs]
|
|
70
|
+
|
|
71
|
+
def start(self):
|
|
72
|
+
"""Start the timestamp fixer service."""
|
|
73
|
+
for obs in self.observers:
|
|
74
|
+
logging.info("starting observer for directory %s", obs.directory)
|
|
75
|
+
obs.start()
|
|
76
|
+
for obs in self.observers:
|
|
77
|
+
obs.join()
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def maybe_fix_time(filename):
|
|
81
|
+
"""Fix filename modified timestamp: if it is in the future, reset it to now."""
|
|
82
|
+
stat = os.stat(filename)
|
|
83
|
+
|
|
84
|
+
secs_ago = round(time.time() - stat.st_mtime)
|
|
85
|
+
logger.info("%s was modified %s seconds ago", filename, secs_ago)
|
|
86
|
+
|
|
87
|
+
if secs_ago >= 0:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
logger.info(
|
|
91
|
+
"modified time stamp for file '%s' is %ss in the future. fixing...",
|
|
92
|
+
filename, abs(secs_ago))
|
|
93
|
+
os.utime(filename, None)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def onchange(change_type, filename, event):
|
|
97
|
+
"""onchange callback for fileobserver"""
|
|
98
|
+
time.sleep(2)
|
|
99
|
+
TimestampFixer.maybe_fix_time(filename)
|
|
100
|
+
if change_type == "moved":
|
|
101
|
+
TimestampFixer.maybe_fix_time(event.dest_path)
|
|
102
|
+
|
|
103
|
+
def install_systemd(name, run_cmd, environment, as_user = True,
|
|
104
|
+
description = None):
|
|
105
|
+
"""Installs the given command as a systemd service with the given name."""
|
|
106
|
+
if as_user:
|
|
107
|
+
config = os.path.expanduser(f"~/.config/systemd/user/{name}.service")
|
|
108
|
+
else:
|
|
109
|
+
config = f"/etc/systemd/system/{name}.service"
|
|
110
|
+
|
|
111
|
+
environment_str = ",".join(
|
|
112
|
+
f"{k}={v}" for k, v in environment.items())
|
|
113
|
+
|
|
114
|
+
description = description or f"{name} Service"
|
|
115
|
+
contents = f"""
|
|
116
|
+
[Unit]
|
|
117
|
+
Description={description}
|
|
118
|
+
|
|
119
|
+
[Service]
|
|
120
|
+
ExecStart={" ".join(run_cmd)}
|
|
121
|
+
Environment={environment_str}
|
|
122
|
+
|
|
123
|
+
[Install]
|
|
124
|
+
WantedBy=default.target
|
|
125
|
+
"""
|
|
126
|
+
with open(config, "w") as fh:
|
|
127
|
+
fh.write(contents)
|
|
128
|
+
|
|
129
|
+
unit = f"{name}.service"
|
|
130
|
+
if as_user:
|
|
131
|
+
start_commands = [["systemctl", "--user", "enable", unit],
|
|
132
|
+
["systemctl", "--user", "daemon-reload"],
|
|
133
|
+
["systemctl", "--user", "restart", unit]]
|
|
134
|
+
else:
|
|
135
|
+
start_commands = [["sudo", "systemctl", "enable", unit],
|
|
136
|
+
["sudo", "systemctl", "daemon-reload"],
|
|
137
|
+
["sudo", "systemctl", "restart", unit]]
|
|
138
|
+
for cmd in start_commands:
|
|
139
|
+
subprocess.run(cmd, check = True)
|
|
140
|
+
logging.info("succesfully installed %s with contents: \n%s", unit, contents)
|
|
141
|
+
|
|
142
|
+
def install_time_fixer_service(dirs, as_user = True):
|
|
143
|
+
"""Install the timefixer serrvice."""
|
|
144
|
+
name = "timefixer"
|
|
145
|
+
run_cmd = ["python3", "-u", __file__, "-q", "-d"] + dirs
|
|
146
|
+
environment = {"PYTHONUNBUFFERED": "1"}
|
|
147
|
+
if not as_user:
|
|
148
|
+
subprocess.call(["sudo", "apt-get", "install", "-y", "python3-watchdog"])
|
|
149
|
+
install_systemd(name=name, run_cmd=run_cmd, environment=environment,
|
|
150
|
+
as_user = as_user)
|
|
151
|
+
|
|
152
|
+
def main():
|
|
153
|
+
"""Main function."""
|
|
154
|
+
parser = argparse.ArgumentParser()
|
|
155
|
+
default_dirs = ["~/Downloads", "~/pictures/auto-scrots", "~/git/3d/", "~/uploads/"]
|
|
156
|
+
parser.add_argument("-d", "--dirs",
|
|
157
|
+
help="directory to observe",
|
|
158
|
+
nargs="+",
|
|
159
|
+
default=list(os.path.realpath(os.path.expanduser(dirname))
|
|
160
|
+
for dirname in default_dirs))
|
|
161
|
+
parser.add_argument("-q", "--quiet", help="quiet", action="store_true")
|
|
162
|
+
parser.add_argument("-i", "--install-systemd", help="install", action="store_true")
|
|
163
|
+
args = parser.parse_args()
|
|
164
|
+
|
|
165
|
+
level = logging.INFO if args.quiet else logging.DEBUG
|
|
166
|
+
logging.basicConfig(level=level)
|
|
167
|
+
|
|
168
|
+
print(f"starting timefixer with log-level {logging.getLevelName(level)}...")
|
|
169
|
+
if args.install:
|
|
170
|
+
install_time_fixer_service(args.dirs)
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
fixer = TimestampFixer(args.dirs)
|
|
174
|
+
fixer.start()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
main()
|
|
179
|
+
|
|
180
|
+
# Local Variables:
|
|
181
|
+
# compile-command: "systemctl --user restart timefixer"
|
|
182
|
+
# compile-command: "./timefixer.py -i"
|
|
183
|
+
# End:
|