accli 2.0.2__tar.gz → 2.0.4__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.
- {accli-2.0.2 → accli-2.0.4}/PKG-INFO +3 -3
- {accli-2.0.2 → accli-2.0.4}/README.md +2 -2
- {accli-2.0.2 → accli-2.0.4}/accli/_version.py +1 -1
- {accli-2.0.2 → accli-2.0.4}/accli/cleanup-nfs.ps1 +7 -1
- {accli-2.0.2 → accli-2.0.4}/accli/cli.py +89 -3
- accli-2.0.4/accli/refresh_explorer.ps1 +12 -0
- {accli-2.0.2 → accli-2.0.4}/accli/setup-nfs.ps1 +33 -1
- accli-2.0.4/accli/windows_refresher.py +159 -0
- {accli-2.0.2 → accli-2.0.4}/accli.egg-info/PKG-INFO +3 -3
- {accli-2.0.2 → accli-2.0.4}/accli.egg-info/SOURCES.txt +2 -0
- {accli-2.0.2 → accli-2.0.4}/LICENSE +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/AcceleratorJobProjectService.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/AcceleratorTaskDispatcher.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/AcceleratorTerminalCliProjectService.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/CsvRegionalTimeseriesValidator.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/__init__.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/__main__.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/common.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/gui.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/mount_downloader.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli/token.py +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli.egg-info/dependency_links.txt +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli.egg-info/entry_points.txt +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli.egg-info/requires.txt +0 -0
- {accli-2.0.2 → accli-2.0.4}/accli.egg-info/top_level.txt +0 -0
- {accli-2.0.2 → accli-2.0.4}/pyproject.toml +0 -0
- {accli-2.0.2 → accli-2.0.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: accli
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: IIASA Accelerator Client
|
|
5
5
|
Author-email: Wrufesh S <wrufesh@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -141,10 +141,10 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
|
|
|
141
141
|
1. Copy and paste certificates from `<control_services_backend>/certs/ca.crt` as
|
|
142
142
|
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/ca.crt` as
|
|
143
143
|
`./certs/dev-minio-ca.crt`.
|
|
144
|
-
2. Run
|
|
144
|
+
2. Run as Administrator
|
|
145
145
|
```
|
|
146
146
|
cd scripts
|
|
147
|
-
import_certs_certutil.ps1
|
|
147
|
+
.\import_certs_certutil.ps1
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
### 🛠 Build & Upload
|
|
@@ -97,10 +97,10 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
|
|
|
97
97
|
1. Copy and paste certificates from `<control_services_backend>/certs/ca.crt` as
|
|
98
98
|
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/ca.crt` as
|
|
99
99
|
`./certs/dev-minio-ca.crt`.
|
|
100
|
-
2. Run
|
|
100
|
+
2. Run as Administrator
|
|
101
101
|
```
|
|
102
102
|
cd scripts
|
|
103
|
-
import_certs_certutil.ps1
|
|
103
|
+
.\import_certs_certutil.ps1
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
### 🛠 Build & Upload
|
|
@@ -34,10 +34,16 @@ if (Get-Command Unregister-ScheduledTask -ErrorAction SilentlyContinue) {
|
|
|
34
34
|
Write-Host "[OK] Scheduled Tasks removed." -ForegroundColor Green
|
|
35
35
|
|
|
36
36
|
# 2. Delete Registry Policy Keys
|
|
37
|
-
Write-Host "Removing registry policies (EnableLinkedConnections, AnonymousUid, AnonymousGid)..." -ForegroundColor Yellow
|
|
37
|
+
Write-Host "Removing registry policies (EnableLinkedConnections, AnonymousUid, AnonymousGid, Cache)..." -ForegroundColor Yellow
|
|
38
38
|
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "EnableLinkedConnections" -ErrorAction SilentlyContinue | Out-Null
|
|
39
39
|
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Default" -Name "AnonymousUid" -ErrorAction SilentlyContinue | Out-Null
|
|
40
40
|
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Default" -Name "AnonymousGid" -ErrorAction SilentlyContinue | Out-Null
|
|
41
|
+
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Default" -Name "DisableCache" -ErrorAction SilentlyContinue | Out-Null
|
|
42
|
+
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Default" -Name "MaxDirCacheSize" -ErrorAction SilentlyContinue | Out-Null
|
|
43
|
+
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Users\Default\Cache" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
|
|
44
|
+
Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Users" -ErrorAction SilentlyContinue |
|
|
45
|
+
Where-Object { $_.PSChildName -like "S-1-5-*" } |
|
|
46
|
+
ForEach-Object { Remove-Item -Path "$($_.PSPath)\Cache" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null }
|
|
41
47
|
Write-Host "[OK] Registry policies removed." -ForegroundColor Green
|
|
42
48
|
|
|
43
49
|
# 3. Delete Config Folder & Helpers
|
|
@@ -604,6 +604,76 @@ def find_available_windows_drive(preferred: str = "W") -> str:
|
|
|
604
604
|
raise RuntimeError("No available Windows drive letters found.")
|
|
605
605
|
|
|
606
606
|
|
|
607
|
+
def start_windows_explorer_refresher(mount_point: str, interval: int):
|
|
608
|
+
"""Starts a lightweight background watcher in the user session that notifies Explorer when remote changes occur."""
|
|
609
|
+
import sys
|
|
610
|
+
import subprocess
|
|
611
|
+
from pathlib import Path
|
|
612
|
+
|
|
613
|
+
refresher_script = Path(__file__).parent / "windows_refresher.py"
|
|
614
|
+
if not refresher_script.is_file():
|
|
615
|
+
return
|
|
616
|
+
|
|
617
|
+
# Locate Python interpreter (prefer pythonw to run completely windowless if available)
|
|
618
|
+
py_exec = sys.executable
|
|
619
|
+
parent_dir = Path(py_exec).parent
|
|
620
|
+
pythonw = parent_dir / "pythonw.exe"
|
|
621
|
+
if pythonw.is_file():
|
|
622
|
+
executable = str(pythonw)
|
|
623
|
+
else:
|
|
624
|
+
executable = py_exec
|
|
625
|
+
|
|
626
|
+
try:
|
|
627
|
+
# DETACHED_PROCESS (0x00000008) | CREATE_NEW_PROCESS_GROUP (0x00000200) | CREATE_NO_WINDOW (0x08000000)
|
|
628
|
+
creationflags = 0x08000008 | 0x00000200
|
|
629
|
+
proc = subprocess.Popen(
|
|
630
|
+
[executable, str(refresher_script), str(mount_point), str(interval)],
|
|
631
|
+
creationflags=creationflags,
|
|
632
|
+
stdout=subprocess.DEVNULL,
|
|
633
|
+
stderr=subprocess.DEVNULL,
|
|
634
|
+
close_fds=True
|
|
635
|
+
)
|
|
636
|
+
pid_file = Path("C:/ProgramData/accli/refresher.pid")
|
|
637
|
+
try:
|
|
638
|
+
pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
639
|
+
pid_file.write_text(str(proc.pid), encoding="utf-8")
|
|
640
|
+
except Exception:
|
|
641
|
+
pass
|
|
642
|
+
except Exception:
|
|
643
|
+
pass
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def stop_windows_explorer_refresher():
|
|
647
|
+
"""Stops any active background Explorer watcher process."""
|
|
648
|
+
import subprocess
|
|
649
|
+
from pathlib import Path
|
|
650
|
+
|
|
651
|
+
pid_file = Path("C:/ProgramData/accli/refresher.pid")
|
|
652
|
+
if pid_file.is_file():
|
|
653
|
+
try:
|
|
654
|
+
pid_str = pid_file.read_text(encoding="utf-8").strip()
|
|
655
|
+
if pid_str:
|
|
656
|
+
subprocess.run(["taskkill", "/F", "/PID", pid_str], capture_output=True)
|
|
657
|
+
except Exception:
|
|
658
|
+
pass
|
|
659
|
+
try:
|
|
660
|
+
pid_file.unlink()
|
|
661
|
+
except Exception:
|
|
662
|
+
pass
|
|
663
|
+
|
|
664
|
+
# Also clean up any lingering windows_refresher.py processes
|
|
665
|
+
try:
|
|
666
|
+
cleanup_cmd = [
|
|
667
|
+
"powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command",
|
|
668
|
+
"Get-CimInstance Win32_Process -Filter \"name='python.exe' or name='pythonw.exe'\" | "
|
|
669
|
+
"Where-Object { $_.CommandLine -like '*windows_refresher.py*' } | "
|
|
670
|
+
"ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
|
|
671
|
+
]
|
|
672
|
+
subprocess.run(cleanup_cmd, capture_output=True)
|
|
673
|
+
except Exception:
|
|
674
|
+
pass
|
|
675
|
+
|
|
676
|
+
|
|
607
677
|
def enable_windows_nfs_features():
|
|
608
678
|
"""Automatically check and enable Windows Client for NFS features, registry key, and Task Scheduler gateway if needed."""
|
|
609
679
|
import subprocess
|
|
@@ -631,6 +701,13 @@ def enable_windows_nfs_features():
|
|
|
631
701
|
" if ($null -ne $valUid -and $valUid.AnonymousUid -eq 0 -and $null -ne $valGid -and $valGid.AnonymousGid -eq 0) { $anonEnabled = $true }\n"
|
|
632
702
|
"}\n"
|
|
633
703
|
"\n"
|
|
704
|
+
"$cacheEnabled = $false\n"
|
|
705
|
+
"$cachePath = 'HKLM:\\SOFTWARE\\Microsoft\\ClientForNFS\\CurrentVersion\\Users\\Default\\Cache'\n"
|
|
706
|
+
"if (Test-Path $cachePath) {\n"
|
|
707
|
+
" $valDelta = Get-ItemProperty -Path $cachePath -Name 'AttributeTimeDelta' -ErrorAction SilentlyContinue\n"
|
|
708
|
+
" if ($null -ne $valDelta -and $valDelta.AttributeTimeDelta -ge 1) { $cacheEnabled = $true }\n"
|
|
709
|
+
"}\n"
|
|
710
|
+
"\n"
|
|
634
711
|
"$taskEnabled = $false\n"
|
|
635
712
|
"if (Get-Command Get-ScheduledTask -ErrorAction SilentlyContinue) {\n"
|
|
636
713
|
" $t = Get-ScheduledTask -TaskName 'accli-mount-nfs' -ErrorAction SilentlyContinue\n"
|
|
@@ -640,7 +717,7 @@ def enable_windows_nfs_features():
|
|
|
640
717
|
" if ($LASTEXITCODE -eq 0) { $taskEnabled = $true }\n"
|
|
641
718
|
"}\n"
|
|
642
719
|
"\n"
|
|
643
|
-
"Write-Output \"NFS:$nfsEnabled;REG:$regEnabled;ANON:$anonEnabled;TASK:$taskEnabled\""
|
|
720
|
+
"Write-Output \"NFS:$nfsEnabled;REG:$regEnabled;ANON:$anonEnabled;CACHE:$cacheEnabled;TASK:$taskEnabled\""
|
|
644
721
|
)
|
|
645
722
|
|
|
646
723
|
check_cmd = [
|
|
@@ -651,6 +728,7 @@ def enable_windows_nfs_features():
|
|
|
651
728
|
nfs_ok = False
|
|
652
729
|
reg_ok = False
|
|
653
730
|
anon_ok = False
|
|
731
|
+
cache_ok = False
|
|
654
732
|
task_ok = False
|
|
655
733
|
|
|
656
734
|
try:
|
|
@@ -663,13 +741,15 @@ def enable_windows_nfs_features():
|
|
|
663
741
|
reg_ok = (part.split(':')[1].lower() == 'true')
|
|
664
742
|
elif part.startswith("ANON:"):
|
|
665
743
|
anon_ok = (part.split(':')[1].lower() == 'true')
|
|
744
|
+
elif part.startswith("CACHE:"):
|
|
745
|
+
cache_ok = (part.split(':')[1].lower() == 'true')
|
|
666
746
|
elif part.startswith("TASK:"):
|
|
667
747
|
task_ok = (part.split(':')[1].lower() == 'true')
|
|
668
748
|
except Exception as e:
|
|
669
749
|
print(f"[yellow]Warning: Could not audit current Windows NFS settings: {e}[/yellow]")
|
|
670
750
|
|
|
671
|
-
# Require
|
|
672
|
-
reg_ok = reg_ok and anon_ok
|
|
751
|
+
# Require general registry settings, Anonymous mappings, and Cache policies to be correct
|
|
752
|
+
reg_ok = reg_ok and anon_ok and cache_ok
|
|
673
753
|
|
|
674
754
|
if nfs_ok and reg_ok and task_ok:
|
|
675
755
|
print("[bold green][OK] Windows NFS client features, registry policies, and Task Scheduler gateway are already configured.[/bold green]")
|
|
@@ -986,6 +1066,7 @@ def mount_start(
|
|
|
986
1066
|
]
|
|
987
1067
|
mount_res = subprocess.run(mount_cmd, capture_output=True, text=True)
|
|
988
1068
|
if mount_res.returncode == 0:
|
|
1069
|
+
start_windows_explorer_refresher(str(mount_point_abs), interval=30)
|
|
989
1070
|
print(f"[bold green][OK] NFS mount successfully mapped at [white]{mount_point_abs}[/white]![/bold green]")
|
|
990
1071
|
print("[cyan]Use 'umount' or 'accli mount stop' to unmount the drive.[/cyan]")
|
|
991
1072
|
return
|
|
@@ -1039,6 +1120,7 @@ def mount_start(
|
|
|
1039
1120
|
print(f"[yellow]Please check the log file at {log_file_path} for more details.[/yellow]")
|
|
1040
1121
|
raise typer.Exit(1)
|
|
1041
1122
|
|
|
1123
|
+
start_windows_explorer_refresher(str(mount_point_abs), interval=30)
|
|
1042
1124
|
print(f"[bold green][OK] NFS mount process spawned successfully (PID: {process.pid}).[/bold green]")
|
|
1043
1125
|
print(f"[cyan]Use 'umount' or 'accli mount stop' to unmount the drive.[/cyan]")
|
|
1044
1126
|
else:
|
|
@@ -1086,6 +1168,7 @@ def mount_start(
|
|
|
1086
1168
|
]
|
|
1087
1169
|
mount_res = subprocess.run(mount_cmd, capture_output=True, text=True)
|
|
1088
1170
|
if mount_res.returncode == 0:
|
|
1171
|
+
start_windows_explorer_refresher(str(mount_point_abs), interval=30)
|
|
1089
1172
|
print(f"[bold green][OK] NFS mount successfully mapped at [white]{mount_point_abs}[/white]![/bold green]")
|
|
1090
1173
|
print("[cyan]Use 'umount' or 'accli mount stop' to unmount the drive.[/cyan]")
|
|
1091
1174
|
else:
|
|
@@ -1223,6 +1306,9 @@ def mount_stop(
|
|
|
1223
1306
|
except Exception:
|
|
1224
1307
|
is_admin = False
|
|
1225
1308
|
|
|
1309
|
+
# 0. Stop Explorer background refresher
|
|
1310
|
+
stop_windows_explorer_refresher()
|
|
1311
|
+
|
|
1226
1312
|
# 1. Unmap the network drive in the user session
|
|
1227
1313
|
res = subprocess.run(["C:\\Windows\\System32\\umount.exe", "-f", str(mount_point_abs)], capture_output=True, text=True)
|
|
1228
1314
|
if res.returncode != 0:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
$ErrorActionPreference = "SilentlyContinue"
|
|
2
|
+
$mountPoint = $args[0]
|
|
3
|
+
if (-not $mountPoint) { $mountPoint = "W:" }
|
|
4
|
+
$mountClean = $mountPoint.TrimEnd("\/")
|
|
5
|
+
|
|
6
|
+
$shell = New-Object -ComObject Shell.Application
|
|
7
|
+
foreach ($win in $shell.Windows()) {
|
|
8
|
+
$url = $win.LocationURL
|
|
9
|
+
if ($url -and ($url -like "*$mountClean*" -or $win.Path -like "*$mountClean*")) {
|
|
10
|
+
$win.Refresh()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -75,6 +75,39 @@ if (-not $RegAlreadyEnabled) {
|
|
|
75
75
|
Write-Host "[OK] Anonymous UID/GID registry values already configured (skipped)." -ForegroundColor Green
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
# 2.6 Configure Client for NFS Cache (fast directory refresh without caching delay)
|
|
79
|
+
Write-Host "Configuring Client for NFS directory cache timeouts..." -ForegroundColor Cyan
|
|
80
|
+
|
|
81
|
+
# Global redirector controls:
|
|
82
|
+
# DisableCache = 0 allows file content caching in memory
|
|
83
|
+
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Default" -Name "DisableCache" -Value 0 -PropertyType DWORD -Force | Out-Null
|
|
84
|
+
# MaxDirCacheSize = 0 bypasses directory tree RAM cache so newly created/deleted files appear immediately
|
|
85
|
+
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Default" -Name "MaxDirCacheSize" -Value 0 -PropertyType DWORD -Force | Out-Null
|
|
86
|
+
|
|
87
|
+
$cacheTargets = @(
|
|
88
|
+
"HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Users\Default\Cache"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
$existingUsers = Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Users" -ErrorAction SilentlyContinue |
|
|
92
|
+
Where-Object { $_.PSChildName -like "S-1-5-*" }
|
|
93
|
+
|
|
94
|
+
foreach ($u in $existingUsers) {
|
|
95
|
+
$cacheTargets += "$($u.PSPath)\Cache"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
foreach ($targetPath in $cacheTargets) {
|
|
99
|
+
if (-not (Test-Path $targetPath)) {
|
|
100
|
+
New-Item -Path $targetPath -Force | Out-Null
|
|
101
|
+
}
|
|
102
|
+
# Enable the attribute cache
|
|
103
|
+
New-ItemProperty -Path $targetPath -Name "FileAttributeCache" -Value 1 -PropertyType DWORD -Force | Out-Null
|
|
104
|
+
# AttributeTimeDelta: metadata TTL (10s) to align with --metadata-ttl-ms 10000
|
|
105
|
+
New-ItemProperty -Path $targetPath -Name "AttributeTimeDelta" -Value 10 -PropertyType DWORD -Force | Out-Null
|
|
106
|
+
# CacheRefreshInterval: file content cache TTL (10s) so modified files refresh within 10s
|
|
107
|
+
New-ItemProperty -Path $targetPath -Name "CacheRefreshInterval" -Value 10 -PropertyType DWORD -Force | Out-Null
|
|
108
|
+
}
|
|
109
|
+
Write-Host "[OK] Client for NFS balanced cache policy configured (10s TTL, No Dir Cache)." -ForegroundColor Green
|
|
110
|
+
|
|
78
111
|
# 3. Create public ProgramData folder and configure permissions for Authenticated Users
|
|
79
112
|
Write-Host "Initializing public accli configuration directory..." -ForegroundColor Cyan
|
|
80
113
|
$configDir = "C:\ProgramData\accli"
|
|
@@ -126,7 +159,6 @@ try {
|
|
|
126
159
|
}
|
|
127
160
|
} catch {}
|
|
128
161
|
|
|
129
|
-
# Construct arguments
|
|
130
162
|
$mountArgs = @(
|
|
131
163
|
"--token-file", $cfg.db_path,
|
|
132
164
|
"--hub-endpoint", $cfg.server_url,
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import ctypes
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
# Windows Shell Notification constants
|
|
10
|
+
SHCNE_UPDATEDIR = 0x00001000
|
|
11
|
+
SHCNF_PATHW = 0x0005
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def log(msg: str):
|
|
15
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
16
|
+
print(f"[{timestamp}] [Watcher] {msg}", flush=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def notify_explorer(path_str: str):
|
|
20
|
+
"""Notifies Windows Shell / Explorer to refresh views for path_str."""
|
|
21
|
+
log(f"Broadcasting SHChangeNotify on '{path_str}'...")
|
|
22
|
+
try:
|
|
23
|
+
ctypes.windll.shell32.SHChangeNotify(
|
|
24
|
+
SHCNE_UPDATEDIR,
|
|
25
|
+
SHCNF_PATHW,
|
|
26
|
+
path_str,
|
|
27
|
+
None
|
|
28
|
+
)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
log(f"SHChangeNotify error: {e}")
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
script_path = Path(__file__).parent / "refresh_explorer.ps1"
|
|
34
|
+
if script_path.is_file():
|
|
35
|
+
log(f"Executing COM window refresh for '{path_str}'...")
|
|
36
|
+
res = subprocess.run(
|
|
37
|
+
[
|
|
38
|
+
"powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
|
|
39
|
+
"-File", str(script_path), path_str
|
|
40
|
+
],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
creationflags=0x08000000 # CREATE_NO_WINDOW
|
|
44
|
+
)
|
|
45
|
+
if res.stdout and res.stdout.strip():
|
|
46
|
+
log(f"COM output: {res.stdout.strip()}")
|
|
47
|
+
if res.stderr and res.stderr.strip():
|
|
48
|
+
log(f"COM error: {res.stderr.strip()}")
|
|
49
|
+
log("Explorer windows refreshed.")
|
|
50
|
+
except Exception as e:
|
|
51
|
+
log(f"COM refresh error: {e}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def scan_directory_state(root_path: Path):
|
|
55
|
+
"""
|
|
56
|
+
Fast snapshot of directory state: mapping of relative_path -> (size, mtime_ns).
|
|
57
|
+
Returns None if root_path is not accessible or unmounted.
|
|
58
|
+
"""
|
|
59
|
+
state = {}
|
|
60
|
+
try:
|
|
61
|
+
if not root_path.exists():
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
for root, dirs, files in os.walk(root_path):
|
|
65
|
+
for name in files:
|
|
66
|
+
full_path = os.path.join(root, name)
|
|
67
|
+
try:
|
|
68
|
+
st = os.stat(full_path)
|
|
69
|
+
rel = os.path.relpath(full_path, root_path)
|
|
70
|
+
state[rel] = (st.st_size, st.st_mtime_ns)
|
|
71
|
+
except (OSError, PermissionError):
|
|
72
|
+
continue
|
|
73
|
+
for name in dirs:
|
|
74
|
+
full_path = os.path.join(root, name)
|
|
75
|
+
try:
|
|
76
|
+
st = os.stat(full_path)
|
|
77
|
+
rel = os.path.relpath(full_path, root_path)
|
|
78
|
+
state[rel] = (-1, st.st_mtime_ns)
|
|
79
|
+
except (OSError, PermissionError):
|
|
80
|
+
continue
|
|
81
|
+
return state
|
|
82
|
+
except Exception:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def describe_changes(old_state, new_state):
|
|
87
|
+
old_keys = set(old_state.keys())
|
|
88
|
+
new_keys = set(new_state.keys())
|
|
89
|
+
added = new_keys - old_keys
|
|
90
|
+
removed = old_keys - new_keys
|
|
91
|
+
modified = {k for k in (old_keys & new_keys) if old_state[k] != new_state[k]}
|
|
92
|
+
|
|
93
|
+
parts = []
|
|
94
|
+
if added:
|
|
95
|
+
parts.append(f"Added: {list(added)[:5]}")
|
|
96
|
+
if removed:
|
|
97
|
+
parts.append(f"Removed: {list(removed)[:5]}")
|
|
98
|
+
if modified:
|
|
99
|
+
parts.append(f"Modified: {list(modified)[:5]}")
|
|
100
|
+
return "; ".join(parts)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def run_watcher(mount_point: str, interval: float):
|
|
104
|
+
"""
|
|
105
|
+
Periodically inspects mount_point for remote additions/deletions/modifications.
|
|
106
|
+
Triggers Windows Shell & open Explorer window refreshes whenever changes are detected.
|
|
107
|
+
"""
|
|
108
|
+
drive_root = mount_point.rstrip("\\/") + "\\"
|
|
109
|
+
root_path = Path(drive_root)
|
|
110
|
+
|
|
111
|
+
log(f"Starting watcher on '{drive_root}' (poll interval: {interval}s)...")
|
|
112
|
+
|
|
113
|
+
# Baseline snapshot
|
|
114
|
+
try:
|
|
115
|
+
last_state = scan_directory_state(root_path)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
last_state = None
|
|
118
|
+
log(f"Initial scan error: {e}")
|
|
119
|
+
|
|
120
|
+
if last_state is not None:
|
|
121
|
+
log(f"Initial baseline snapshot indexed {len(last_state)} items on '{drive_root}'.")
|
|
122
|
+
else:
|
|
123
|
+
log(f"Warning: Could not read '{drive_root}'. Waiting for drive to become available...")
|
|
124
|
+
|
|
125
|
+
while True:
|
|
126
|
+
try:
|
|
127
|
+
time.sleep(interval)
|
|
128
|
+
except (KeyboardInterrupt, SystemExit):
|
|
129
|
+
log("Watcher stopped.")
|
|
130
|
+
break
|
|
131
|
+
except Exception as e:
|
|
132
|
+
log(f"Sleep interrupted: {e}")
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
current_state = scan_directory_state(root_path)
|
|
137
|
+
if current_state is None:
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
if last_state is not None:
|
|
141
|
+
if current_state != last_state:
|
|
142
|
+
diff_summary = describe_changes(last_state, current_state)
|
|
143
|
+
log(f"Change detected on '{drive_root}' -> {diff_summary}")
|
|
144
|
+
notify_explorer(drive_root)
|
|
145
|
+
last_state = current_state
|
|
146
|
+
else:
|
|
147
|
+
last_state = current_state
|
|
148
|
+
log(f"Baseline established: {len(last_state)} items found.")
|
|
149
|
+
except Exception as e:
|
|
150
|
+
log(f"Loop error: {e}")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
if len(sys.argv) < 3:
|
|
155
|
+
print("Usage: python windows_refresher.py <mount_point> <interval_seconds>")
|
|
156
|
+
sys.exit(1)
|
|
157
|
+
mp = sys.argv[1]
|
|
158
|
+
poll_int = float(sys.argv[2])
|
|
159
|
+
run_watcher(mp, poll_int)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: accli
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: IIASA Accelerator Client
|
|
5
5
|
Author-email: Wrufesh S <wrufesh@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -141,10 +141,10 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
|
|
|
141
141
|
1. Copy and paste certificates from `<control_services_backend>/certs/ca.crt` as
|
|
142
142
|
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/ca.crt` as
|
|
143
143
|
`./certs/dev-minio-ca.crt`.
|
|
144
|
-
2. Run
|
|
144
|
+
2. Run as Administrator
|
|
145
145
|
```
|
|
146
146
|
cd scripts
|
|
147
|
-
import_certs_certutil.ps1
|
|
147
|
+
.\import_certs_certutil.ps1
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
### 🛠 Build & Upload
|
|
@@ -13,8 +13,10 @@ accli/cli.py
|
|
|
13
13
|
accli/common.py
|
|
14
14
|
accli/gui.py
|
|
15
15
|
accli/mount_downloader.py
|
|
16
|
+
accli/refresh_explorer.ps1
|
|
16
17
|
accli/setup-nfs.ps1
|
|
17
18
|
accli/token.py
|
|
19
|
+
accli/windows_refresher.py
|
|
18
20
|
accli.egg-info/PKG-INFO
|
|
19
21
|
accli.egg-info/SOURCES.txt
|
|
20
22
|
accli.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|