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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ README.md
2
+ pyproject.toml
3
+ timefixer.py
4
+ timefixer.egg-info/PKG-INFO
5
+ timefixer.egg-info/SOURCES.txt
6
+ timefixer.egg-info/dependency_links.txt
7
+ timefixer.egg-info/entry_points.txt
8
+ timefixer.egg-info/requires.txt
9
+ timefixer.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ timefixer = timefixer.timefixer:main
3
+
4
+ [timefixer.timefixer]
5
+ timefixer = timefixer.timefixer:main
@@ -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: