@@ -16,6 +16,7 @@ pub enum Error {
1616 ParsingLockfile ( std:: num:: ParseIntError ) ,
1717 ReadingLockfile ( std:: io:: Error ) ,
1818 WritingLockfile ( std:: io:: Error ) ,
19+ ProjectFolderMissing ( std:: path:: PathBuf ) ,
1920}
2021
2122impl std:: fmt:: Display for Error {
@@ -31,6 +32,10 @@ impl std::fmt::Display for Error {
3132 format ! ( "Could not read lockfile: \n {e} \n (try removing it and running the command again)" )
3233 }
3334 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+ ) ,
3439 } ;
3540 write ! ( f, "{msg}" )
3641 }
@@ -41,35 +46,92 @@ pub enum Lock {
4146 Error ( Error ) ,
4247}
4348
44- fn exists ( to_check_pid : u32 ) -> bool {
49+ fn pid_exists ( to_check_pid : u32 ) -> bool {
4550 System :: new_all ( )
4651 . processes ( )
4752 . iter ( )
4853 . any ( |( pid, _process) | pid. as_u32 ( ) == to_check_pid)
4954}
5055
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-
6256pub 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 ) ;
6465 let pid = process:: id ( ) ;
6566
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.
6669 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 our own 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) ) ,
7388 } ,
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+ ) ;
74136 }
75137}
0 commit comments