1use crate::core::{SymlinkInfo, TwinError, TwinResult};
9use std::fs;
10use std::path::Path;
11#[cfg(windows)]
12use std::process::Command;
13
14pub trait SymlinkManager {
16 fn create_symlink(&self, source: &Path, target: &Path) -> TwinResult<SymlinkInfo>;
18
19 fn remove_symlink(&self, path: &Path) -> TwinResult<()>;
21
22 #[allow(dead_code)]
24 fn validate_symlink(&self, path: &Path) -> TwinResult<bool>;
25
26 #[allow(dead_code)]
28 fn get_manual_instructions(&self, source: &Path, target: &Path) -> String;
29}
30
31#[cfg(unix)]
33#[allow(dead_code)]
34pub type PlatformSymlinkManager = UnixSymlinkManager;
35
36#[cfg(windows)]
37#[allow(dead_code)]
38pub type PlatformSymlinkManager = WindowsSymlinkManager;
39
40#[cfg(unix)]
42pub struct UnixSymlinkManager;
43
44#[cfg(unix)]
45impl UnixSymlinkManager {
46 pub fn new() -> Self {
47 Self
48 }
49}
50
51#[cfg(unix)]
52impl Default for UnixSymlinkManager {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58#[cfg(unix)]
59impl SymlinkManager for UnixSymlinkManager {
60 fn create_symlink(&self, source: &Path, target: &Path) -> TwinResult<SymlinkInfo> {
61 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok() {
63 eprintln!(
64 "🔗 シンボリックリンク作成: {} -> {}",
65 target.display(),
66 source.display()
67 );
68 }
69
70 if !source.exists() {
72 return Err(TwinError::symlink(
73 format!("Source path does not exist: {}", source.display()),
74 Some(source.to_path_buf()),
75 ));
76 }
77
78 if target.exists() || target.is_symlink() {
80 fs::remove_file(target).ok();
81 }
82
83 if let Some(parent) = target.parent() {
85 fs::create_dir_all(parent)?;
86 }
87
88 #[cfg(unix)]
90 {
91 use std::os::unix::fs::symlink;
92 match symlink(source, target) {
93 Ok(_) => {
94 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok()
95 {
96 eprintln!("✅ シンボリックリンク作成成功");
97 }
98 let mut info = SymlinkInfo::new(source.to_path_buf(), target.to_path_buf());
99 info.set_success();
100 Ok(info)
101 }
102 Err(e) => {
103 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok()
104 {
105 eprintln!("❌ シンボリックリンク作成失敗: {}", e);
106 }
107 let mut info = SymlinkInfo::new(source.to_path_buf(), target.to_path_buf());
108 info.set_error(format!("Failed to create symlink: {}", e));
109 Err(TwinError::symlink(
110 format!("Failed to create symlink: {}", e),
111 Some(target.to_path_buf()),
112 ))
113 }
114 }
115 }
116 }
117
118 fn remove_symlink(&self, path: &Path) -> TwinResult<()> {
119 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok() {
120 eprintln!("🗑️ シンボリックリンク削除: {}", path.display());
121 }
122
123 if path.is_symlink() {
124 fs::remove_file(path)?;
125 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok() {
126 eprintln!("✅ シンボリックリンク削除成功");
127 }
128 }
129 Ok(())
130 }
131
132 #[allow(dead_code)]
133 fn validate_symlink(&self, path: &Path) -> TwinResult<bool> {
134 if !path.exists() {
135 return Ok(false);
136 }
137
138 let metadata = fs::symlink_metadata(path)?;
140 if !metadata.file_type().is_symlink() {
141 return Ok(false);
142 }
143
144 match fs::metadata(path) {
146 Ok(_) => Ok(true),
147 Err(_) => Ok(false), }
149 }
150
151 #[allow(dead_code)]
152 fn get_manual_instructions(&self, source: &Path, target: &Path) -> String {
153 format!(
154 "To manually create the symlink, run:\n ln -s \"{}\" \"{}\"",
155 source.display(),
156 target.display()
157 )
158 }
159}
160
161#[cfg(windows)]
163pub struct WindowsSymlinkManager {
164 developer_mode: bool,
166 is_elevated: bool,
168}
169
170#[cfg(windows)]
171impl Default for WindowsSymlinkManager {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177#[cfg(windows)]
178impl WindowsSymlinkManager {
179 pub fn new() -> Self {
180 Self {
181 developer_mode: Self::check_developer_mode(),
182 is_elevated: Self::check_elevation(),
183 }
184 }
185
186 fn check_developer_mode() -> bool {
188 let output = Command::new("reg")
190 .args([
191 "query",
192 "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
193 "/v",
194 "AllowDevelopmentWithoutDevLicense",
195 ])
196 .output();
197
198 if let Ok(output) = output {
199 let stdout = String::from_utf8_lossy(&output.stdout);
200 return stdout.contains("0x1");
201 }
202
203 false
204 }
205
206 fn check_elevation() -> bool {
208 Command::new("net")
210 .args(["session"])
211 .output()
212 .map(|o| o.status.success())
213 .unwrap_or(false)
214 }
215
216 fn copy_file(&self, source: &Path, target: &Path) -> TwinResult<()> {
218 if let Some(parent) = target.parent() {
219 fs::create_dir_all(parent)?;
220 }
221
222 fs::copy(source, target)?;
223 Ok(())
224 }
225}
226
227#[cfg(windows)]
228impl SymlinkManager for WindowsSymlinkManager {
229 fn create_symlink(&self, source: &Path, target: &Path) -> TwinResult<SymlinkInfo> {
230 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok() {
232 eprintln!(
233 "🔗 シンボリックリンク作成: {} -> {}",
234 target.display(),
235 source.display()
236 );
237 }
238
239 if !source.exists() {
241 return Err(TwinError::symlink(
242 format!("Source path does not exist: {}", source.display()),
243 Some(source.to_path_buf()),
244 ));
245 }
246
247 if target.exists() {
249 fs::remove_file(target).ok();
250 fs::remove_dir(target).ok();
251 }
252
253 if let Some(parent) = target.parent() {
255 fs::create_dir_all(parent)?;
256 }
257
258 let result = if self.developer_mode || self.is_elevated {
260 #[cfg(windows)]
262 {
263 use std::os::windows::fs::{symlink_dir, symlink_file};
264 if source.is_dir() {
265 symlink_dir(source, target).map_err(|e| {
266 TwinError::symlink(
267 format!("Failed to create directory symlink: {e}"),
268 Some(target.to_path_buf()),
269 )
270 })
271 } else {
272 symlink_file(source, target).map_err(|e| {
273 TwinError::symlink(
274 format!("Failed to create file symlink: {e}"),
275 Some(target.to_path_buf()),
276 )
277 })
278 }
279 }
280 } else {
281 eprintln!(
283 "⚠️ Warning: Symbolic link creation requires Developer Mode or Administrator privileges"
284 );
285 eprintln!("⚠️ Falling back to file copy instead");
286 self.copy_file(source, target)
287 };
288
289 let mut info = SymlinkInfo::new(source.to_path_buf(), target.to_path_buf());
290
291 match result {
292 Ok(_) => {
293 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok() {
294 eprintln!("✅ シンボリックリンク作成成功");
295 }
296 info.set_success();
297 Ok(info)
298 }
299 Err(e) => {
300 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok() {
301 eprintln!("❌ シンボリックリンク作成失敗: {e}");
302 }
303 info.set_error(e.to_string());
304 Err(e)
305 }
306 }
307 }
308
309 fn remove_symlink(&self, path: &Path) -> TwinResult<()> {
310 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok() {
311 eprintln!("🗑️ シンボリックリンク削除: {}", path.display());
312 }
313
314 if path.exists() {
315 let metadata = fs::symlink_metadata(path)?;
316 if metadata.is_dir() {
317 fs::remove_dir(path)?;
318 } else {
319 fs::remove_file(path)?;
320 }
321 if std::env::var("TWIN_VERBOSE").is_ok() || std::env::var("TWIN_DEBUG").is_ok() {
322 eprintln!("✅ シンボリックリンク削除成功");
323 }
324 }
325 Ok(())
326 }
327
328 #[allow(dead_code)]
329 fn validate_symlink(&self, path: &Path) -> TwinResult<bool> {
330 if !path.exists() {
331 return Ok(false);
332 }
333
334 #[cfg(windows)]
336 {
337 use std::os::windows::fs::MetadataExt;
338 let metadata = fs::symlink_metadata(path)?;
339 let attrs = metadata.file_attributes();
340
341 const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;
343 if attrs & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
344 return match fs::metadata(path) {
346 Ok(_) => Ok(true),
347 Err(_) => Ok(false),
348 };
349 }
350 }
351
352 Ok(false)
353 }
354
355 #[allow(dead_code)]
356 fn get_manual_instructions(&self, source: &Path, target: &Path) -> String {
357 if source.is_dir() {
358 format!(
359 "mklink /D \"{}\" \"{}\"",
360 target.display(),
361 source.display()
362 )
363 } else {
364 format!("mklink \"{}\" \"{}\"", target.display(), source.display())
365 }
366 }
367}
368
369pub fn create_symlink_manager() -> Box<dyn SymlinkManager> {
371 #[cfg(unix)]
372 {
373 Box::new(UnixSymlinkManager::new())
374 }
375
376 #[cfg(windows)]
377 {
378 Box::new(WindowsSymlinkManager::new())
379 }
380}