github.com/jpbruinsslot/dot@v0.0.0-20231229172555-797f05ba0642/file.go (about) 1 // file.go will hold all the operations that have to do with 2 // file management. 3 4 package main 5 6 import ( 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "log" 12 "os" 13 "path" 14 "path/filepath" 15 "strings" 16 ) 17 18 func SyncFiles() { 19 PrintHeader("Syncing files ...") 20 21 // load config 22 c, err := NewConfig(PathDotConfig) 23 if err != nil { 24 PrintBodyError( 25 "not able to load config file. Make sure the .dotconfig file is present and points to the correct location", 26 ) 27 return 28 } 29 30 // when we have files the sync them 31 if len(c.Files) > 0 { 32 // for every file track it 33 copyAll := false 34 for name, path := range c.Files { 35 // get full path 36 fullPath := fmt.Sprintf("%s%s", HomeDir(), path) 37 copyAll = TrackFile(name, fullPath, false, copyAll) 38 } 39 } else { 40 PrintBodyError("there aren't any files being tracked. Begin doing so with: `dot add -name [name] -path [path]`") 41 } 42 } 43 44 // TrackFile will track an individual file, meaning, it will move the original 45 // file to either the files or backup folder. It will the create a symlink of 46 // the file in the original location. `name` will be used as the name of the 47 // folder and key in the config file. `fullPath` has to be the absolute path 48 // to the file to be tracked. 49 // 50 // TrackFile can be called from two contexes: 51 // 52 // 1. From SyncFiles, it will read all the tracked files from the config and 53 // make track files if necessary. 54 // 55 // 2. From CommandAdd, this will add a new file for tracking 56 // 57 // TrackFile will make a distinction between a new file and a file that is 58 // already been tracked: 59 // 60 // 1. TrackFile can't find the symlink, but the file is present in the 61 // dot_path (the folder that holds all the original files). Then we need to 62 // relink it, thus creating a symlink at the correct location. This happens 63 // we you run dot on a new 'additional machine'. 64 // 65 // 2. TrackFile can't find the symlink, and the file is also not present in 66 // the dot_path folder. This will mean that it is a new file were are going 67 // to track. So we copy the file to the files folder, create a symlink, and 68 // add an entry to the config file. 69 func TrackFile(name string, fullPath string, push bool, copyAll bool) bool { 70 // load config 71 c, err := NewConfig(PathDotConfig) 72 if err != nil { 73 PrintBodyError("not able to find .dotconfig") 74 return copyAll 75 } 76 77 // Base 78 base := path.Base(fullPath) 79 80 // get relative path 81 relPath, err := GetRelativePath(fullPath) 82 if err != nil { 83 PrintBodyError(err.Error()) 84 return copyAll 85 } 86 87 // check if path is present 88 _, err = os.Stat(fullPath) 89 if err != nil { 90 PrintBodyError(fmt.Sprintf("file not present on system: %s", fullPath)) 91 92 if copyAll { 93 src := fmt.Sprintf("%s%s/files/%s/%s", HomeDir(), c.DotPath, name, base) 94 MakeAndCopyToDir(src, fullPath) 95 return TrackFile(name, fullPath, push, copyAll) 96 } 97 98 PrintBody("Copy file(s) to its destination? [All/Y/N]") 99 var input string 100 _, err := fmt.Scan(&input) 101 if err != nil { 102 log.Fatal(err) 103 } 104 105 switch input { 106 case "All": 107 copyAll = true 108 src := fmt.Sprintf("%s%s/files/%s/%s", HomeDir(), c.DotPath, name, base) 109 err := MakeAndCopyToDir(src, fullPath) 110 if err != nil { 111 PrintBodyError(err.Error()) 112 return copyAll 113 } 114 return TrackFile(name, fullPath, push, copyAll) 115 case "Y": 116 src := fmt.Sprintf("%s%s/files/%s/%s", HomeDir(), c.DotPath, name, base) 117 err := MakeAndCopyToDir(src, fullPath) 118 if err != nil { 119 PrintBodyError(err.Error()) 120 return copyAll 121 } 122 return TrackFile(name, fullPath, push, copyAll) 123 case "N": 124 PrintBodyError(fmt.Sprintf("Ignoring %s", name)) 125 return copyAll 126 default: 127 PrintBodyError("Invalid input") 128 return copyAll 129 } 130 } 131 132 // check if path is already symlinked 133 s, err := os.Lstat(fullPath) 134 if err != nil { 135 return copyAll 136 } 137 138 if s.Mode()&os.ModeSymlink == os.ModeSymlink { 139 PrintBody(fmt.Sprintf("%s is already symlinked", name)) 140 return copyAll 141 } 142 143 repoPath := fmt.Sprintf("%s%s/files/%s/", HomeDir(), c.DotPath, name) 144 if _, err := os.Stat(repoPath); err == nil { 145 // no symlink found, already in repo => additional machine 146 PrintBody(fmt.Sprintf("Symlinking: %s", name)) 147 148 // put in backup folder, set named folder based on `name`, e.g.: 149 // `/home/jpbruinsslot/dotfiles/backup/[name]/[base]` 150 dst := fmt.Sprintf("%s%s/backup/%s/%s", HomeDir(), c.DotPath, name, base) 151 err = MakeAndMoveToDir(fullPath, dst) 152 if err != nil { 153 msg := fmt.Sprintf("not able to move files to %s (%s)", dst, err) 154 PrintBodyError(msg) 155 156 prompt := fmt.Sprintf("Remove %s ? [Y/N]", dst) 157 PrintBody(prompt) 158 var input string 159 _, err := fmt.Scan(&input) 160 if err != nil { 161 log.Fatal(err) 162 } 163 164 if input == "Y" { 165 err := os.RemoveAll(dst) 166 if err != nil { 167 log.Fatal(err) 168 } 169 170 err = MakeAndMoveToDir(fullPath, dst) 171 if err != nil { 172 log.Fatal(err) 173 } 174 } else { 175 msg := fmt.Sprintf("ignoring %s", name) 176 PrintBodyError(msg) 177 return copyAll 178 } 179 } 180 181 // trim potential trailing slash for symlink 182 fullPath = strings.TrimRight(fullPath, "/") 183 184 // create symlink (os.Symlink(oldname, newname)) 185 dst = fmt.Sprintf("%s%s/files/%s/%s", HomeDir(), c.DotPath, name, base) 186 err = os.Symlink(dst, fullPath) 187 if err != nil { 188 log.Fatal(err) 189 } 190 191 } else { 192 // no symlink found, not in repo => new entry 193 PrintBody(fmt.Sprintf("Symlinking: %s", name)) 194 195 // put in files folder, set named folder based on `name`, e.g.: 196 // `/home/jpbruinsslot/dotfiles/files/[name]/[base]` 197 dst := fmt.Sprintf("%s%s/files/%s/%s", HomeDir(), c.DotPath, name, base) 198 err = MakeAndMoveToDir(fullPath, dst) 199 if err != nil { 200 log.Fatal(err) 201 } 202 203 // trim potential trailing slash for symlink 204 fullPath = strings.TrimRight(fullPath, "/") 205 206 // create symlink (os.Symlink(oldname, newname)) 207 err = os.Symlink(dst, fullPath) 208 if err != nil { 209 log.Fatal(err) 210 } 211 212 // create entry in .dotconfig file 213 c.Files[name] = relPath 214 c.Save() 215 216 // push changes to repository 217 if push { 218 GitCommitPush(name, "add") 219 } 220 } 221 222 return copyAll 223 } 224 225 // UntrackFile will remove a file from tracking. `name` will be the key 226 // in the config file that points to the initial location of the file 227 func UntrackFile(name string, push bool) { 228 // open config file 229 c, err := NewConfig(fmt.Sprintf("%s/%s", HomeDir(), ConfigFileName)) 230 if err != nil { 231 PrintBodyError("not able to find .dotconfig") 232 return 233 } 234 235 // check if `name` is present in c.Files 236 path := c.Files[name] 237 if path == "" { 238 PrintBodyError( 239 fmt.Sprintf( 240 "'%s' is not being tracked. Get the list of tracked files with `dot list`", 241 name, 242 ), 243 ) 244 return 245 } 246 247 // check if path (the symlink) is present 248 pathSymlink := fmt.Sprintf("%s%s", HomeDir(), path) 249 f, err := os.Lstat(pathSymlink) 250 if err != nil { 251 PrintBodyError(fmt.Sprintf("not able to find: %s", path)) 252 return 253 } 254 255 // check if path is symlink 256 if f.Mode()&os.ModeSymlink != os.ModeSymlink { 257 PrintBodyError(fmt.Sprintf("%s is not a symlink", path)) 258 return 259 } 260 261 // check if src is present 262 src := fmt.Sprintf("%s%s/files/%s%s", HomeDir(), c.DotPath, name, path) 263 if _, err = os.Stat(src); err != nil { 264 PrintBodyError(fmt.Sprintf("not able to find %s", src)) 265 return 266 } 267 268 // remove symlink 269 err = os.Remove(pathSymlink) 270 if err != nil { 271 PrintBodyError(fmt.Sprintf("not able to remove %s", pathSymlink)) 272 return 273 } 274 275 // move the file or directory 276 dst := fmt.Sprintf("%s%s", HomeDir(), path) 277 278 PrintBody(fmt.Sprintf("Moving %s back to %s", name, dst)) 279 280 err = MakeAndCopyToDir(src, dst) 281 if err != nil { 282 log.Fatal(err) 283 } 284 285 // remove tracked files from repo dir 286 entry := fmt.Sprintf("%s%s/files/%s", HomeDir(), c.DotPath, name) 287 err = os.RemoveAll(entry) 288 if err != nil { 289 log.Fatal(err) 290 } 291 292 // remove entry from config and save config 293 delete(c.Files, name) 294 c.Save() 295 296 // push changes to repository 297 if push { 298 GitCommitPush(name, "rm") 299 } 300 } 301 302 // MakeAndMoveToDir will move the source file/folder `src` to the destination 303 // `dst` (`dst` will be absolute path to the destination). 304 func MakeAndMoveToDir(src string, dst string) error { 305 err := MakeAndCopyToDir(src, dst) 306 if err != nil { 307 return err 308 } 309 310 err = os.RemoveAll(src) 311 if err != nil { 312 return err 313 } 314 315 return nil 316 } 317 318 func MakeAndCopyToDir(src string, dst string) error { 319 // folder or file 320 f, err := os.Stat(src) 321 if err != nil { 322 return err 323 } 324 325 if f.IsDir() { 326 err = CopyDir(src, dst) 327 if err != nil { 328 return err 329 } 330 } else { 331 // get directory 332 dir, _ := filepath.Split(dst) 333 334 // create destination dir 335 // err = os.MkdirAll(dir, f.Mode()) 336 err = os.MkdirAll(dir, 0755) 337 if err != nil { 338 return err 339 } 340 341 // rename the file 342 err = CopyFile(src, dst) 343 if err != nil { 344 return err 345 } 346 } 347 348 return nil 349 } 350 351 func CopyDir(src string, dst string) error { 352 src = filepath.Clean(src) 353 dst = filepath.Clean(dst) 354 355 // Check src 356 f, err := os.Stat(src) 357 if err != nil { 358 return err 359 } 360 361 if !f.IsDir() { 362 return errors.New("source is not a directory") 363 } 364 365 // Check dst 366 _, err = os.Stat(dst) 367 if err != nil && !os.IsNotExist(err) { 368 return nil 369 } 370 if err == nil { 371 return errors.New("dst already exist") 372 } 373 374 files, err := ioutil.ReadDir(src) 375 if err != nil { 376 return err 377 } 378 379 os.MkdirAll(dst, f.Mode()) 380 if err != nil { 381 return err 382 } 383 384 for _, file := range files { 385 srcPath := filepath.Join(src, file.Name()) 386 dstPath := filepath.Join(dst, file.Name()) 387 388 if file.IsDir() { 389 err = CopyDir(srcPath, dstPath) 390 if err != nil { 391 return err 392 } 393 } else { 394 if file.Mode()&os.ModeSymlink != 0 { 395 continue 396 } 397 398 err = CopyFile(srcPath, dstPath) 399 if err != nil { 400 return err 401 } 402 } 403 } 404 405 return nil 406 } 407 408 func CopyFile(src string, dst string) error { 409 inFile, err := os.Open(src) 410 if err != nil { 411 return err 412 } 413 defer inFile.Close() 414 415 outFile, err := os.Create(dst) 416 if err != nil { 417 return err 418 } 419 defer outFile.Close() 420 421 _, err = io.Copy(outFile, inFile) 422 if err != nil { 423 return err 424 } 425 426 err = outFile.Sync() 427 if err != nil { 428 return err 429 } 430 431 f, err := os.Stat(src) 432 if err != nil { 433 return err 434 } 435 436 err = os.Chmod(dst, f.Mode()) 437 if err != nil { 438 return err 439 } 440 441 return nil 442 }