1
1
package dev .dirs .impl ;
2
2
3
- import dev . dirs . Constants ;
3
+ import java . util . function . Supplier ;
4
4
5
- import java .io .BufferedReader ;
6
- import java .io .File ;
7
- import java .io .IOException ;
8
- import java .io .InputStreamReader ;
9
- import java .lang .reflect .Method ;
10
- import java .nio .charset .Charset ;
11
-
12
- public class Windows {
13
-
14
- private Windows () {}
15
-
16
- private static final String UTF8_BOM = "\ufeff " ;
17
- private static Object base64Encoder = null ;
18
- private static Method base64EncodeMethod = null ;
19
- // This string needs to end up being a multiple of 3 bytes after conversion to UTF-16. (It is currently 1200 bytes.)
20
- // This is because Base64 converts 3 bytes to 4 letters; other numbers of bytes would introduce padding, which
21
- // would make it harder to simply concatenate this precomputed string with whatever directories the user requests.
22
- static final String SCRIPT_START_BASE64 = Constants .operatingSystem == 'w' ? toUTF16LEBase64 ("& {\n " +
23
- "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n " +
24
- "Add-Type @\" \n " +
25
- "using System;\n " +
26
- "using System.Runtime.InteropServices;\n " +
27
- "public class Dir {\n " +
28
- " [DllImport(\" shell32.dll\" )]\n " +
29
- " private static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr pszPath);\n " +
30
- " public static string GetKnownFolderPath(string rfid) {\n " +
31
- " IntPtr pszPath;\n " +
32
- " if (SHGetKnownFolderPath(new Guid(rfid), 0, IntPtr.Zero, out pszPath) != 0) return \" \" ;\n " +
33
- " string path = Marshal.PtrToStringUni(pszPath);\n " +
34
- " Marshal.FreeCoTaskMem(pszPath);\n " +
35
- " return path;\n " +
36
- " }\n " +
37
- "}\n " +
38
- "\" @\n " ) : null ;
39
-
40
- public static String [] getWinDirs (String ... guids ) {
41
- int guidsLength = guids .length ;
42
- StringBuilder buf = new StringBuilder (guidsLength * 68 );
43
- for (int i = 0 ; i < guidsLength ; i ++) {
44
- buf .append ("[Dir]::GetKnownFolderPath(\" " );
45
- buf .append (guids [i ]);
46
- buf .append ("\" )\n " );
47
- }
48
-
49
- String encodedCommand = SCRIPT_START_BASE64 + toUTF16LEBase64 (buf + "}" );
50
- String path = System .getenv ("Path" );
51
- String [] dirs = path == null ? new String [0 ] : path .split (File .pathSeparator );
52
- if (dirs .length == 0 ) {
53
- return windowsFallback (guidsLength , encodedCommand );
54
- }
55
- try {
56
- return runWinCommands (guidsLength , dirs , encodedCommand );
57
- } catch (IOException e ) {
58
- return windowsFallback (guidsLength , encodedCommand );
59
- }
60
- }
61
-
62
- private static String toUTF16LEBase64 (String script ) {
63
- byte [] scriptInUtf16LEBytes = script .getBytes (Charset .forName ("UTF-16LE" ));
64
- if (base64EncodeMethod == null ) {
65
- initBase64Encoding ();
66
- }
67
- try {
68
- return (String ) base64EncodeMethod .invoke (base64Encoder , scriptInUtf16LEBytes );
69
- } catch (Exception e ) {
70
- throw new RuntimeException ("Base64 encoding failed!" , e );
71
- }
72
- }
73
-
74
- private static void initBase64Encoding () {
75
- try {
76
- base64Encoder = Class .forName ("java.util.Base64" ).getMethod ("getEncoder" ).invoke (null );
77
- base64EncodeMethod = base64Encoder .getClass ().getMethod ("encodeToString" , byte [].class );
78
- } catch (Exception e1 ) {
79
- try {
80
- base64EncodeMethod = Class .forName ("sun.misc.BASE64Encoder" ).getMethod ("encode" , byte [].class );
81
- } catch (Exception e2 ) {
82
- throw new RuntimeException (
83
- "Could not find any viable Base64 encoder! (java.util.Base64 failed with: " + e1 .getMessage () + ")" , e2 );
84
- }
85
- }
86
- }
87
-
88
- private static String [] runCommands (int expectedResultLines , Charset charset , String ... commands ) throws IOException {
89
- final Process process = new ProcessBuilder (commands ).start ();
90
-
91
- String [] results = new String [expectedResultLines ];
92
- BufferedReader reader = new BufferedReader (new InputStreamReader (process .getInputStream (), charset ));
93
- try {
94
- for (int i = 0 ; i < expectedResultLines ; i ++) {
95
- String line = reader .readLine ();
96
- if (i == 0 && Constants .operatingSystem == 'w' && line != null && line .startsWith (UTF8_BOM ))
97
- line = line .substring (UTF8_BOM .length ());
98
- results [i ] = line ;
99
- }
100
- return results ;
101
- } finally {
102
- process .destroy ();
103
- try {
104
- reader .close ();
105
- } catch (IOException e ) {
106
- e .printStackTrace ();
107
- }
108
- }
109
- }
110
-
111
- private static String [] runWinCommands (int guidsLength , String [] dirs , String encodedCommand ) throws IOException {
112
- // legacy powershell.exe seems to run faster than pwsh.exe so prefer it if available
113
- String [] commands = { "powershell.exe" , "pwsh.exe" };
114
- IOException firstException = null ;
115
- for (String dir : dirs ) {
116
- for (String command : commands ) {
117
- File commandFile = new File (dir , command );
118
- if (commandFile .exists ()) {
119
- try {
120
- return runCommands (guidsLength , Charset .forName ("UTF-8" ),
121
- commandFile .toString (),
122
- "-NoProfile" ,
123
- "-EncodedCommand" ,
124
- encodedCommand
125
- );
126
- } catch (IOException e ) {
127
- firstException = firstException == null ? e : firstException ;
128
- }
129
- }
130
- }
131
- }
132
- if (firstException != null ) {
133
- throw firstException ;
134
- }
135
- else throw new IOException ("no directories" );
136
- }
137
-
138
- private static String [] windowsFallback (int guidsLength , String encodedCommand ) {
139
- File powerShellBase = new File ("C:\\ Program Files\\ Powershell" );
140
- String [] powerShellDirs = powerShellBase .list ();
141
- if (powerShellDirs == null ) {
142
- powerShellDirs = new String [0 ];
143
- }
144
- String [] allPowerShellDirs = new String [powerShellDirs .length + 1 ];
145
-
146
- // legacy powershell.exe seems to run faster than pwsh.exe so prefer it if available
147
- String systemRoot = System .getenv ("SystemRoot" );
148
- if (systemRoot == null ) {
149
- systemRoot = "C:\\ Windows" ;
150
- }
151
- allPowerShellDirs [0 ] = systemRoot + "\\ System32\\ WindowsPowerShell\\ v1.0\\ " ;
152
-
153
- for (int i = 0 ; i < powerShellDirs .length ; ++i ) {
154
- allPowerShellDirs [i + 1 ] = new File (powerShellBase , powerShellDirs [i ]).toString ();
155
- }
156
- try {
157
- return runWinCommands (guidsLength , allPowerShellDirs , encodedCommand );
158
- } catch (final IOException ex ) {
159
- throw new RuntimeException ("Couldn't find pwsh.exe or powershell.exe on path or in default system locations" , ex );
160
- }
161
- }
5
+ public interface Windows {
162
6
163
7
public static String applicationPath (String qualifier , String organization , String application ) {
164
8
StringBuilder buf = new StringBuilder (Math .max (Util .stringLength (organization ) + Util .stringLength (application ), 0 ));
@@ -173,4 +17,14 @@ public static String applicationPath(String qualifier, String organization, Stri
173
17
buf .append (application );
174
18
return buf .toString ();
175
19
}
20
+
21
+ String [] winDirs (String ... folderIds );
22
+
23
+ public static String [] getWinDirs (String ... folderIds ) {
24
+ return getDefaultSupplier ().get ().winDirs (folderIds );
25
+ }
26
+
27
+ public static Supplier <Windows > getDefaultSupplier () {
28
+ return () -> WindowsDefault .getDefaultImpl ();
29
+ }
176
30
}
0 commit comments