@@ -16,6 +16,7 @@ pub enum Error {
16
16
ParsingLockfile ( std:: num:: ParseIntError ) ,
17
17
ReadingLockfile ( std:: io:: Error ) ,
18
18
WritingLockfile ( std:: io:: Error ) ,
19
+ ProjectFolderMissing ( std:: path:: PathBuf ) ,
19
20
}
20
21
21
22
impl std:: fmt:: Display for Error {
@@ -31,6 +32,10 @@ impl std::fmt::Display for Error {
31
32
format ! ( "Could not read lockfile: \n {e} \n (try removing it and running the command again)" )
32
33
}
33
34
Error :: WritingLockfile ( e) => format ! ( "Could not write lockfile: \n {e}" ) ,
35
+ Error :: ProjectFolderMissing ( path) => format ! (
36
+ "Could not write lockfile because the specified project folder does not exist: {}" ,
37
+ path. to_string_lossy( )
38
+ ) ,
34
39
} ;
35
40
write ! ( f, "{msg}" )
36
41
}
@@ -41,35 +46,92 @@ pub enum Lock {
41
46
Error ( Error ) ,
42
47
}
43
48
44
- fn exists ( to_check_pid : u32 ) -> bool {
49
+ fn pid_exists ( to_check_pid : u32 ) -> bool {
45
50
System :: new_all ( )
46
51
. processes ( )
47
52
. iter ( )
48
53
. any ( |( pid, _process) | pid. as_u32 ( ) == to_check_pid)
49
54
}
50
55
51
- fn create ( lockfile_location : & Path , pid : u32 ) -> Lock {
52
- // Create /lib if not exists
53
- if let Some ( Err ( e) ) = lockfile_location. parent ( ) . map ( fs:: create_dir_all) {
54
- return Lock :: Error ( Error :: WritingLockfile ( e) ) ;
55
- } ;
56
-
57
- File :: create ( lockfile_location)
58
- . and_then ( |mut file| file. write ( pid. to_string ( ) . as_bytes ( ) ) . map ( |_| Lock :: Aquired ( pid) ) )
59
- . unwrap_or_else ( |e| Lock :: Error ( Error :: WritingLockfile ( e) ) )
60
- }
61
-
62
56
pub fn get ( folder : & str ) -> Lock {
63
- let location = Path :: new ( folder) . join ( "lib" ) . join ( LOCKFILE ) ;
57
+ let project_folder = Path :: new ( folder) ;
58
+ if !project_folder. exists ( ) {
59
+ return Lock :: Error ( Error :: ProjectFolderMissing ( project_folder. to_path_buf ( ) ) ) ;
60
+ }
61
+
62
+ // `lib` sits directly under the provided project folder; compute it once so later code stays simple.
63
+ let lib_dir = project_folder. join ( "lib" ) ;
64
+ let location = lib_dir. join ( LOCKFILE ) ;
64
65
let pid = process:: id ( ) ;
65
66
67
+ // When a lockfile already exists we parse its PID: if the process is still alive we refuse to
68
+ // proceed, otherwise we will overwrite the stale lock with our own PID.
66
69
match fs:: read_to_string ( & location) {
67
- Err ( e) if ( e. kind ( ) == std:: io:: ErrorKind :: NotFound ) => create ( & location, pid) ,
68
- Err ( e) => Lock :: Error ( Error :: ReadingLockfile ( e) ) ,
69
- Ok ( s) => match s. parse :: < u32 > ( ) {
70
- Ok ( parsed_pid) if !exists ( parsed_pid) => create ( & location, pid) ,
71
- Ok ( parsed_pid) => Lock :: Error ( Error :: Locked ( parsed_pid) ) ,
72
- Err ( e) => Lock :: Error ( Error :: ParsingLockfile ( e) ) ,
70
+ Ok ( contents) => match contents. parse :: < u32 > ( ) {
71
+ Ok ( parsed_pid) if pid_exists ( parsed_pid) => return Lock :: Error ( Error :: Locked ( parsed_pid) ) ,
72
+ Ok ( _) => ( ) ,
73
+ Err ( e) => return Lock :: Error ( Error :: ParsingLockfile ( e) ) ,
74
+ } ,
75
+ Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: NotFound => ( ) ,
76
+ Err ( e) => return Lock :: Error ( Error :: ReadingLockfile ( e) ) ,
77
+ }
78
+
79
+ if let Err ( e) = fs:: create_dir_all ( & lib_dir) {
80
+ return Lock :: Error ( Error :: WritingLockfile ( e) ) ;
81
+ }
82
+
83
+ // Rewrite the lockfile with just our PID.
84
+ match File :: create ( & location) {
85
+ Ok ( mut file) => match file. write ( pid. to_string ( ) . as_bytes ( ) ) {
86
+ Ok ( _) => Lock :: Aquired ( pid) ,
87
+ Err ( e) => Lock :: Error ( Error :: WritingLockfile ( e) ) ,
73
88
} ,
89
+ Err ( e) => Lock :: Error ( Error :: WritingLockfile ( e) ) ,
90
+ }
91
+ }
92
+
93
+ #[ cfg( test) ]
94
+ mod tests {
95
+ use super :: * ;
96
+ use std:: fs;
97
+ use tempfile:: TempDir ;
98
+
99
+ #[ test]
100
+ fn returns_error_when_project_folder_missing ( ) {
101
+ let temp_dir = TempDir :: new ( ) . expect ( "temp dir should be created" ) ;
102
+ let missing_folder = temp_dir. path ( ) . join ( "missing_project" ) ;
103
+
104
+ match get ( missing_folder. to_str ( ) . expect ( "path should be valid" ) ) {
105
+ Lock :: Error ( Error :: ProjectFolderMissing ( path) ) => {
106
+ assert_eq ! ( path, missing_folder) ;
107
+ }
108
+ _ => panic ! ( "expected ProjectFolderMissing error" ) ,
109
+ }
110
+
111
+ assert ! (
112
+ !missing_folder. exists( ) ,
113
+ "missing project folder should not be created"
114
+ ) ;
115
+ }
116
+
117
+ #[ test]
118
+ fn creates_lock_when_project_folder_exists ( ) {
119
+ let temp_dir = TempDir :: new ( ) . expect ( "temp dir should be created" ) ;
120
+ let project_folder = temp_dir. path ( ) . join ( "project" ) ;
121
+ fs:: create_dir ( & project_folder) . expect ( "project folder should be created" ) ;
122
+
123
+ match get ( project_folder. to_str ( ) . expect ( "path should be valid" ) ) {
124
+ Lock :: Aquired ( _) => { }
125
+ _ => panic ! ( "expected lock to be acquired" ) ,
126
+ }
127
+
128
+ assert ! (
129
+ project_folder. join( "lib" ) . exists( ) ,
130
+ "lib directory should be created"
131
+ ) ;
132
+ assert ! (
133
+ project_folder. join( "lib" ) . join( LOCKFILE ) . exists( ) ,
134
+ "lockfile should be created"
135
+ ) ;
74
136
}
75
137
}
0 commit comments