@@ -10,8 +10,9 @@ pub use self::tmc_config::{ConfigValue, TmcConfig};
1010use crate :: output:: LocalExercise ;
1111
1212use anyhow:: { Context , Error } ;
13- use std:: env;
14- use std:: path:: PathBuf ;
13+ use std:: path:: { Path , PathBuf } ;
14+ use std:: { collections:: BTreeMap , env, fs} ;
15+ use tmc_langs_framework:: file_util;
1516
1617// base directory for a given plugin's settings files
1718fn get_tmc_dir ( client_name : & str ) -> Result < PathBuf , Error > {
@@ -26,7 +27,8 @@ pub fn list_local_course_exercises(
2627 client_name : & str ,
2728 course_slug : & str ,
2829) -> Result < Vec < LocalExercise > , anyhow:: Error > {
29- let projects_dir = TmcConfig :: load ( client_name) ?. projects_dir ;
30+ let config_path = TmcConfig :: get_location ( client_name) ?;
31+ let projects_dir = TmcConfig :: load ( client_name, & config_path) ?. projects_dir ;
3032 let mut projects_config = ProjectsConfig :: load ( & projects_dir) ?;
3133
3234 let exercises = projects_config
@@ -43,3 +45,185 @@ pub fn list_local_course_exercises(
4345 }
4446 Ok ( local_exercises)
4547}
48+
49+ pub fn migrate (
50+ tmc_config : & TmcConfig ,
51+ course_slug : & str ,
52+ exercise_slug : & str ,
53+ exercise_id : usize ,
54+ exercise_checksum : & str ,
55+ exercise_path : & Path ,
56+ ) -> anyhow:: Result < ( ) > {
57+ let mut lock = file_util:: FileLock :: new ( exercise_path. to_path_buf ( ) ) ?;
58+ let guard = lock. lock ( ) ?;
59+
60+ let mut projects_config = ProjectsConfig :: load ( & tmc_config. projects_dir ) ?;
61+ let course_config = projects_config
62+ . courses
63+ . entry ( course_slug. to_string ( ) )
64+ . or_insert ( CourseConfig {
65+ course : course_slug. to_string ( ) ,
66+ exercises : BTreeMap :: new ( ) ,
67+ } ) ;
68+
69+ let target_dir = ProjectsConfig :: get_exercise_download_target (
70+ & tmc_config. projects_dir ,
71+ course_slug,
72+ exercise_slug,
73+ ) ;
74+ if target_dir. exists ( ) {
75+ anyhow:: bail!(
76+ "Tried to migrate exercise to {}; however, something already exists at that path." ,
77+ target_dir. display( )
78+ ) ;
79+ }
80+
81+ course_config. exercises . insert (
82+ exercise_slug. to_string ( ) ,
83+ Exercise {
84+ id : exercise_id,
85+ checksum : exercise_checksum. to_string ( ) ,
86+ } ,
87+ ) ;
88+
89+ super :: move_dir ( exercise_path, guard, & target_dir) ?;
90+ course_config. save_to_projects_dir ( & tmc_config. projects_dir ) ?;
91+ Ok ( ( ) )
92+ }
93+
94+ pub fn move_projects_dir (
95+ mut tmc_config : TmcConfig ,
96+ config_path : & Path ,
97+ target : PathBuf ,
98+ ) -> anyhow:: Result < ( ) > {
99+ if target. is_file ( ) {
100+ anyhow:: bail!( "The target path points to a file." )
101+ }
102+ if !target. exists ( ) {
103+ fs:: create_dir_all ( & target)
104+ . with_context ( || format ! ( "Failed to create directory at {}" , target. display( ) ) ) ?;
105+ }
106+
107+ let target_canon = target
108+ . canonicalize ( )
109+ . with_context ( || format ! ( "Failed to canonicalize {}" , target. display( ) ) ) ?;
110+ let prev_dir_canon = tmc_config. projects_dir . canonicalize ( ) . with_context ( || {
111+ format ! (
112+ "Failed to canonicalize {}" ,
113+ tmc_config. projects_dir. display( )
114+ )
115+ } ) ?;
116+ if target_canon == prev_dir_canon {
117+ anyhow:: bail!( "Attempted to move the projects-dir to the directory it's already in." )
118+ }
119+
120+ let old_projects_dir = tmc_config. set_projects_dir ( target. clone ( ) ) ?;
121+
122+ let mut lock = file_util:: FileLock :: new ( old_projects_dir. clone ( ) ) ?;
123+ let guard = lock. lock ( ) ?;
124+
125+ super :: move_dir ( & old_projects_dir, guard, & target) ?;
126+ tmc_config. save ( config_path) ?;
127+ Ok ( ( ) )
128+ }
129+
130+ #[ cfg( test) ]
131+ mod test {
132+ use toml:: value:: Table ;
133+
134+ use super :: * ;
135+
136+ fn init ( ) {
137+ use log:: * ;
138+ use simple_logger:: * ;
139+ let _ = SimpleLogger :: new ( ) . with_level ( LevelFilter :: Debug ) . init ( ) ;
140+ }
141+
142+ fn file_to (
143+ target_dir : impl AsRef < std:: path:: Path > ,
144+ target_relative : impl AsRef < std:: path:: Path > ,
145+ contents : impl AsRef < [ u8 ] > ,
146+ ) -> PathBuf {
147+ let target = target_dir. as_ref ( ) . join ( target_relative) ;
148+ if let Some ( parent) = target. parent ( ) {
149+ std:: fs:: create_dir_all ( parent) . unwrap ( ) ;
150+ }
151+ std:: fs:: write ( & target, contents. as_ref ( ) ) . unwrap ( ) ;
152+ target
153+ }
154+
155+ #[ test]
156+ fn migrates ( ) {
157+ init ( ) ;
158+
159+ let projects_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
160+ let exercise_path = tempfile:: tempdir ( ) . unwrap ( ) ;
161+
162+ let tmc_config = TmcConfig {
163+ projects_dir : projects_dir. path ( ) . to_path_buf ( ) ,
164+ table : Table :: new ( ) ,
165+ } ;
166+
167+ file_to ( & exercise_path, "some_file" , "" ) ;
168+
169+ assert ! ( !projects_dir
170+ . path( )
171+ . join( "course/exercise/some_file" )
172+ . exists( ) ) ;
173+
174+ migrate (
175+ & tmc_config,
176+ "course" ,
177+ "exercise" ,
178+ 0 ,
179+ "checksum" ,
180+ exercise_path. path ( ) ,
181+ )
182+ . unwrap ( ) ;
183+
184+ assert ! ( projects_dir
185+ . path( )
186+ . join( "course/exercise/some_file" )
187+ . exists( ) ) ;
188+
189+ assert ! ( !exercise_path. path( ) . exists( ) ) ;
190+ }
191+
192+ #[ test]
193+ fn moves_projects_dir ( ) {
194+ init ( ) ;
195+
196+ let projects_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
197+ let target_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
198+
199+ let config_path = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) ;
200+ let tmc_config = TmcConfig {
201+ projects_dir : projects_dir. path ( ) . to_path_buf ( ) ,
202+ table : Table :: new ( ) ,
203+ } ;
204+
205+ file_to (
206+ projects_dir. path ( ) ,
207+ "some course/some exercise/some file" ,
208+ "" ,
209+ ) ;
210+
211+ assert ! ( !target_dir
212+ . path( )
213+ . join( "some course/some exercise/some file" )
214+ . exists( ) ) ;
215+
216+ move_projects_dir (
217+ tmc_config,
218+ config_path. path ( ) ,
219+ target_dir. path ( ) . to_path_buf ( ) ,
220+ )
221+ . unwrap ( ) ;
222+
223+ assert ! ( target_dir
224+ . path( )
225+ . join( "some course/some exercise/some file" )
226+ . exists( ) ) ;
227+ assert ! ( !projects_dir. path( ) . exists( ) ) ;
228+ }
229+ }
0 commit comments