44import com .typesafe .config .ConfigFactory ;
55import java .io .File ;
66import java .io .IOException ;
7+ import java .io .UncheckedIOException ;
78import java .nio .file .Files ;
9+ import java .nio .file .LinkOption ;
810import java .nio .file .Path ;
911import java .nio .file .Paths ;
1012import java .nio .file .StandardCopyOption ;
11- import java .util .Arrays ;
13+ import java .nio .file .attribute .BasicFileAttributes ;
14+ import java .util .ArrayList ;
1215import java .util .HashSet ;
1316import java .util .List ;
14- import java .util .Objects ;
1517import java .util .Set ;
1618import java .util .concurrent .Callable ;
19+ import java .util .concurrent .atomic .AtomicBoolean ;
1720import java .util .stream .Collectors ;
21+ import java .util .stream .Stream ;
1822import lombok .extern .slf4j .Slf4j ;
1923import me .tongfei .progressbar .ProgressBar ;
2024import org .tron .plugins .utils .FileUtils ;
@@ -76,70 +80,138 @@ public Integer call() throws Exception {
7680 printNotExist ();
7781 return 0 ;
7882 }
79- List <Property > toBeMove = dbs . stream ()
80- . map ( c -> {
81- try {
82- return new Property (c .getString (NAME_CONFIG_KEY ),
83- Paths .get (database .toString (), dbPath , c .getString (NAME_CONFIG_KEY )),
84- Paths .get (c .getString (PATH_CONFIG_KEY ), dbPath , c .getString (NAME_CONFIG_KEY )));
85- } catch (IOException e ) {
86- spec .commandLine ().getErr ().println (e );
87- }
88- return null ;
89- }). filter ( Objects :: nonNull )
90- . filter ( p -> ! p . destination . equals ( p . original )). collect ( Collectors . toList ());
91-
92- if (toBeMove . isEmpty () ) {
93- printNotExist ( );
94- return 0 ;
83+ List <Property > toBeMove = new ArrayList <>();
84+ for ( Config c : dbs ) {
85+ try {
86+ toBeMove . add ( new Property (c .getString (NAME_CONFIG_KEY ),
87+ Paths .get (database .toString (), dbPath , c .getString (NAME_CONFIG_KEY )),
88+ Paths .get (c .getString (PATH_CONFIG_KEY ), dbPath , c .getString (NAME_CONFIG_KEY ) )));
89+ } catch (IOException e ) {
90+ spec .commandLine ().getErr ().println (e );
91+ return 2 ;
92+ }
93+ }
94+ boolean allCopied = ProgressBar . wrap ( toBeMove . stream (), "copy task" )
95+ . allMatch ( this :: copy );
96+ if (! allCopied ) {
97+ cleanupDestinations ( toBeMove );
98+ return 1 ;
9599 }
96- toBeMove = toBeMove .stream ()
97- .filter (property -> {
98- if (property .destination .toFile ().exists ()) {
99- spec .commandLine ().getOut ().println (String .format ("%s already exist,skip." ,
100- property .destination ));
101- return false ;
102- } else {
103- return true ;
104- }
105- }).collect (Collectors .toList ());
106100
107- if (toBeMove .isEmpty ()) {
108- printNotExist ();
109- return 0 ;
101+ boolean allMoved = ProgressBar .wrap (toBeMove .stream (), "link task" )
102+ .map (this ::replaceSourceWithLink ).reduce (Boolean .TRUE , Boolean ::logicalAnd );
103+ if (!allMoved ) {
104+ return 1 ;
110105 }
111- ProgressBar .wrap (toBeMove .stream (), "mv task" ).forEach (this ::run );
112106 spec .commandLine ().getOut ().println ("move db done." );
113-
114107 } else {
115108 printNotExist ();
116109 return 0 ;
117110 }
118111 return 0 ;
119112 }
120113
121- private void run (Property p ) {
122- if (p .destination .toFile ().mkdirs ()) {
123- ProgressBar .wrap (Arrays .stream (Objects .requireNonNull (p .original .toFile ().listFiles ()))
124- .filter (File ::isFile ).map (File ::getName ).parallel (), p .name ).forEach (file -> {
125- Path original = Paths .get (p .original .toString (), file );
126- Path destination = Paths .get (p .destination .toString (), file );
127- try {
128- Files .copy (original , destination ,
129- StandardCopyOption .REPLACE_EXISTING );
130- } catch (IOException e ) {
131- spec .commandLine ().getErr ().println (e );
132- }
133- });
114+ private boolean copy (Property p ) {
115+ if (!p .destination .toFile ().mkdirs ()) {
116+ spec .commandLine ().getErr ().println (String .format ("%s create failed." , p .destination ));
117+ return false ;
118+ }
119+
120+ AtomicBoolean hasError = new AtomicBoolean (false );
121+ try (Stream <Path > files = Files .walk (p .original )) {
122+ ProgressBar .wrap (files .parallel (), p .name ).forEach (source -> {
123+ if (hasError .get ()) {
124+ return ;
125+ }
126+ try {
127+ copyEntry (p , source );
128+ } catch (IOException e ) {
129+ hasError .set (true );
130+ spec .commandLine ().getErr ().println (e );
131+ }
132+ });
133+ } catch (IOException | UncheckedIOException e ) {
134+ hasError .set (true );
135+ spec .commandLine ().getErr ().println (e );
136+ }
137+
138+ if (hasError .get ()) {
139+ spec .commandLine ().getErr ().println (String .format (
140+ "%s copy to %s failed, source kept." ,
141+ p .original , p .destination ));
142+ return false ;
143+ }
144+ return true ;
145+ }
146+
147+ private void copyEntry (Property p , Path source ) throws IOException {
148+ BasicFileAttributes attributes = Files .readAttributes (
149+ source , BasicFileAttributes .class , LinkOption .NOFOLLOW_LINKS );
150+ Path destination = p .destination .resolve (p .original .relativize (source ));
151+ if (attributes .isDirectory ()) {
152+ Files .createDirectories (destination );
153+ } else if (attributes .isRegularFile ()) {
154+ Files .createDirectories (destination .getParent ());
155+ Files .copy (source , destination , StandardCopyOption .REPLACE_EXISTING );
156+ } else {
157+ throw new IOException (String .format (
158+ "%s is neither a regular file nor a directory, can not be moved." , source ));
159+ }
160+ }
161+
162+ private boolean replaceSourceWithLink (Property p ) {
163+ try {
164+ if (!FileUtils .deleteDir (p .original .toFile ())) {
165+ spec .commandLine ().getErr ().println (String .format (
166+ "%s delete failed and may be incomplete; the only complete copy is at %s, keep it." ,
167+ p .original , p .destination ));
168+ printRecoveryHint (p );
169+ return false ;
170+ }
171+ Files .createSymbolicLink (p .original , p .destination );
172+ return true ;
173+ } catch (IOException | RuntimeException x ) {
174+ spec .commandLine ().getErr ().println (x );
175+ spec .commandLine ().getErr ().println (String .format (
176+ "%s move failed; the complete copy is at %s, keep it." ,
177+ p .original , p .destination ));
178+ printRecoveryHint (p );
179+ return false ;
180+ }
181+ }
182+
183+ private void printRecoveryHint (Property p ) {
184+ spec .commandLine ().getErr ().println (String .format (
185+ "To recover manually: remove %s if present, then create a symbolic link at %s"
186+ + " pointing to %s." ,
187+ p .original , p .original , p .destination ));
188+ }
189+
190+ private void cleanupDestinations (List <Property > properties ) {
191+ boolean allCleaned = properties .stream ().map (property -> {
192+ File destination = property .destination .toFile ();
193+ if (Files .notExists (destination .toPath (), LinkOption .NOFOLLOW_LINKS )) {
194+ return true ;
195+ }
134196 try {
135- if (FileUtils .deleteDir (p . original . toFile () )) {
136- Files . createSymbolicLink ( p . original , p . destination ) ;
197+ if (FileUtils .deleteDir (destination )) {
198+ return true ;
137199 }
138- } catch (IOException | UnsupportedOperationException x ) {
139- spec .commandLine ().getErr ().println (x );
200+ } catch (RuntimeException e ) {
201+ spec .commandLine ().getErr ().println (e );
140202 }
203+ spec .commandLine ().getErr ().println (String .format (
204+ "%s cleanup failed; remove the leftover copy before retrying." ,
205+ property .destination ));
206+ return false ;
207+ }).reduce (Boolean .TRUE , Boolean ::logicalAnd );
208+
209+ if (allCleaned ) {
210+ spec .commandLine ().getErr ().println (
211+ "move db failed; all source databases were kept, please retry." );
141212 } else {
142- spec .commandLine ().getErr ().println (String .format ("%s create failed." , p .destination ));
213+ spec .commandLine ().getErr ().println (
214+ "move db failed; all source databases were kept, but leftover copies remain." );
143215 }
144216 }
145217
@@ -167,7 +239,7 @@ public Property(String name, Path original, Path destination) throws IOException
167239 throw new IOException (original + " is symbolicLink!" );
168240 }
169241 this .destination = destination .toFile ().getCanonicalFile ().toPath ();
170- if (this .destination . toFile (). exists ( )) {
242+ if (! Files . notExists ( this .destination , LinkOption . NOFOLLOW_LINKS )) {
171243 throw new IOException (this .destination + " already exist!" );
172244 }
173245 if (this .destination .equals (this .original )) {
@@ -195,9 +267,6 @@ public Config convert(String value) throws Exception {
195267 if (dbs .isEmpty ()) {
196268 throw notFind ;
197269 }
198- String dbPath = config .hasPath (DB_DIRECTORY_CONFIG_KEY )
199- ? config .getString (DB_DIRECTORY_CONFIG_KEY ) : DEFAULT_DB_DIRECTORY ;
200-
201270 dbs = dbs .stream ()
202271 .filter (c -> c .hasPath (NAME_CONFIG_KEY ) && c .hasPath (PATH_CONFIG_KEY ))
203272 .collect (Collectors .toList ());
@@ -207,13 +276,10 @@ public Config convert(String value) throws Exception {
207276 }
208277 Set <String > toBeMove = new HashSet <>();
209278 for (Config c : dbs ) {
210- if (!toBeMove .add (new Property (c .getString (NAME_CONFIG_KEY ),
211- Paths .get (database .toString (), dbPath , c .getString (NAME_CONFIG_KEY )),
212- Paths .get (c .getString (PATH_CONFIG_KEY ), dbPath ,
213- c .getString (NAME_CONFIG_KEY ))).name )) {
279+ String name = c .getString (NAME_CONFIG_KEY );
280+ if (!toBeMove .add (name )) {
214281 throw new IllegalArgumentException (
215- "DB config has duplicate key:[" + c .getString (NAME_CONFIG_KEY )
216- + "],please check! " );
282+ "DB config has duplicate key:[" + name + "],please check! " );
217283 }
218284 }
219285 } else {
0 commit comments