* ConsoleRunner:
- Fix webapps file path * SusiDNS: - Fix addressbook file path * Systray: - Fix NPE if no config file - Fix config file path * WorkingDir: - Modify clients.config so jetty can find the jetty.xml file - Rip out all the existing-installation migration code - Rip out migration code now done by izpack parsable - Fix copy of empty directories
This commit is contained in:
@ -74,6 +74,19 @@ public class RouterConsoleRunner {
|
|||||||
props.setProperty(PREFIX + ROUTERCONSOLE + ENABLED, "true");
|
props.setProperty(PREFIX + ROUTERCONSOLE + ENABLED, "true");
|
||||||
rewrite = true;
|
rewrite = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get an absolute path with a trailing slash for the webapps dir
|
||||||
|
// We assume relative to the base install dir for backward compatibility
|
||||||
|
File app = new File(_webAppsDir);
|
||||||
|
if (!app.isAbsolute()) {
|
||||||
|
app = new File(I2PAppContext.getGlobalContext().getBaseDir(), _webAppsDir);
|
||||||
|
try {
|
||||||
|
_webAppsDir = app.getCanonicalPath();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
if (!_webAppsDir.endsWith("/"))
|
||||||
|
_webAppsDir += '/';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
|
StringTokenizer tok = new StringTokenizer(_listenHost, " ,");
|
||||||
int boundAddresses = 0;
|
int boundAddresses = 0;
|
||||||
@ -99,7 +112,7 @@ public class RouterConsoleRunner {
|
|||||||
tmpdir.mkdir();
|
tmpdir.mkdir();
|
||||||
wac.setTempDirectory(tmpdir);
|
wac.setTempDirectory(tmpdir);
|
||||||
initialize(wac);
|
initialize(wac);
|
||||||
File dir = new File(I2PAppContext.getGlobalContext().getBaseDir(), _webAppsDir);
|
File dir = new File(_webAppsDir);
|
||||||
String fileNames[] = dir.list(WarFilenameFilter.instance());
|
String fileNames[] = dir.list(WarFilenameFilter.instance());
|
||||||
if (fileNames != null) {
|
if (fileNames != null) {
|
||||||
for (int i = 0; i < fileNames.length; i++) {
|
for (int i = 0; i < fileNames.length; i++) {
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package i2p.susi.dns;
|
package i2p.susi.dns;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -110,9 +111,12 @@ public class AddressbookBean
|
|||||||
{
|
{
|
||||||
loadConfig();
|
loadConfig();
|
||||||
String filename = properties.getProperty( getBook() + "_addressbook" );
|
String filename = properties.getProperty( getBook() + "_addressbook" );
|
||||||
if (filename.startsWith("../"))
|
// clean up the ../ with getCanonicalPath()
|
||||||
return filename.substring(3);
|
File path = new File(ConfigBean.addressbookPrefix, filename);
|
||||||
return ConfigBean.addressbookPrefix + filename;
|
try {
|
||||||
|
return path.getCanonicalPath();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
return filename;
|
||||||
}
|
}
|
||||||
private Object[] entries;
|
private Object[] entries;
|
||||||
public Object[] getEntries()
|
public Object[] getEntries()
|
||||||
|
@ -60,11 +60,10 @@ public class ConfigFile {
|
|||||||
_properties.load(fileInputStream);
|
_properties.load(fileInputStream);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
rv = false;
|
rv = false;
|
||||||
|
} finally {
|
||||||
|
if (fileInputStream != null) {
|
||||||
|
try { fileInputStream.close(); } catch (IOException e) {}
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
fileInputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// No worries.
|
|
||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
@ -78,11 +77,10 @@ public class ConfigFile {
|
|||||||
_properties.store(fileOutputStream, null);
|
_properties.store(fileOutputStream, null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
rv = false;
|
rv = false;
|
||||||
|
} finally {
|
||||||
|
if (fileOutputStream != null) {
|
||||||
|
try { fileOutputStream.close(); } catch (IOException e) {}
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
fileOutputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// No worries.
|
|
||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,12 @@
|
|||||||
package net.i2p.apps.systray;
|
package net.i2p.apps.systray;
|
||||||
|
|
||||||
import java.awt.Frame;
|
import java.awt.Frame;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.util.SimpleScheduler;
|
import net.i2p.util.SimpleScheduler;
|
||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleTimer;
|
||||||
|
|
||||||
import snoozesoft.systray4j.SysTrayMenu;
|
import snoozesoft.systray4j.SysTrayMenu;
|
||||||
import snoozesoft.systray4j.SysTrayMenuEvent;
|
import snoozesoft.systray4j.SysTrayMenuEvent;
|
||||||
import snoozesoft.systray4j.SysTrayMenuIcon;
|
import snoozesoft.systray4j.SysTrayMenuIcon;
|
||||||
@ -36,7 +39,8 @@ public class SysTray implements SysTrayMenuListener {
|
|||||||
private static UrlLauncher _urlLauncher = new UrlLauncher();
|
private static UrlLauncher _urlLauncher = new UrlLauncher();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
if (!_configFile.init("systray.config")) {
|
File config = new File(I2PAppContext.getGlobalContext().getConfigDir(), "systray.config");
|
||||||
|
if (!_configFile.init(config.getAbsolutePath())) {
|
||||||
_configFile.setProperty("browser", "default");
|
_configFile.setProperty("browser", "default");
|
||||||
_configFile.setProperty("port", "7657");
|
_configFile.setProperty("port", "7657");
|
||||||
}
|
}
|
||||||
|
@ -120,36 +120,11 @@ public class WorkingDir {
|
|||||||
boolean success = migrate(MIGRATE_BASE, oldDirf, dirf);
|
boolean success = migrate(MIGRATE_BASE, oldDirf, dirf);
|
||||||
// this one must be after MIGRATE_BASE
|
// this one must be after MIGRATE_BASE
|
||||||
success &= migrateJettyXml(oldDirf, dirf);
|
success &= migrateJettyXml(oldDirf, dirf);
|
||||||
success &= migrateWrapperConfig(oldDirf, dirf);
|
success &= migrateClientsConfig(oldDirf, dirf);
|
||||||
if (migrateOldData) {
|
|
||||||
success &= migrate(MIGRATE_DATA, oldDirf, dirf);
|
|
||||||
success &= migrateI2PTunnelKeys(oldDirf, dirf);
|
|
||||||
success &= migrateSnark(oldDirf, dirf);
|
|
||||||
// new installs will have updated scripts left in the install dir
|
|
||||||
// don't bother migrating runplain.sh or i2prouter.bat
|
|
||||||
if (!isWindows)
|
|
||||||
success &= migrateI2prouter(oldDirf, dirf);
|
|
||||||
} else if (!oldInstall) {
|
|
||||||
// copy the default i2psnark.config over
|
|
||||||
success &= migrate("i2psnark.config", oldDirf, dirf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report success or failure
|
// Report success or failure
|
||||||
if (success) {
|
if (success) {
|
||||||
System.err.println("Successfully copied data files to new user directory " + rv);
|
System.err.println("Successfully copied data files to new user directory " + rv);
|
||||||
if (migrateOldData) {
|
|
||||||
System.err.println("Libraries and other files remain in the old directory " + cwd + ", do not remove them.");
|
|
||||||
System.err.println("You should manually move any non-standard files, such as additional eepsite directories and key files");
|
|
||||||
System.err.println("After verifying that all is working, you may delete the following data files and directories in " +
|
|
||||||
cwd + ": " + MIGRATE_DATA.replace(',', ' ') + " i2psnark.config tmp work");
|
|
||||||
if (System.getProperty("wrapper.version") != null)
|
|
||||||
System.err.println("Note that until you shutdown your router completely and restart, the wrapper will continue" +
|
|
||||||
" to log to the old wrapper logs in " + cwd);
|
|
||||||
if (!isWindows)
|
|
||||||
System.err.println("From now on, you should now use the i2prouter" +
|
|
||||||
" script in the " + rv + " directory to start i2p." +
|
|
||||||
" You may copy or move this script elsewhere, you need not run it from that directory.");
|
|
||||||
}
|
|
||||||
return rv;
|
return rv;
|
||||||
} else {
|
} else {
|
||||||
System.err.println("FAILED copy of some or all data files to new directory " + rv);
|
System.err.println("FAILED copy of some or all data files to new directory " + rv);
|
||||||
@ -173,27 +148,9 @@ public class WorkingDir {
|
|||||||
// base install - files
|
// base install - files
|
||||||
// We don't currently have a default router.config or logger.config in the base distribution,
|
// We don't currently have a default router.config or logger.config in the base distribution,
|
||||||
// but distros might put one in
|
// but distros might put one in
|
||||||
"blocklist.txt,clients.config,hosts.txt,i2ptunnel.config,jetty-i2psnark.xml," +
|
"blocklist.txt,hosts.txt,i2psnark.config,i2ptunnel.config,jetty-i2psnark.xml," +
|
||||||
"logger.config,router.config,systray.config";
|
"logger.config,router.config,systray.config";
|
||||||
|
|
||||||
/**
|
|
||||||
* files and directories from an old single-directory installation to copy over - NOT including snark
|
|
||||||
* None of these should be included in i2pupdate.zip
|
|
||||||
*
|
|
||||||
* The user can be advised to delete these from the old location
|
|
||||||
*/
|
|
||||||
private static final String MIGRATE_DATA =
|
|
||||||
// post install - dirs
|
|
||||||
// not required to copy - tmp/, work/
|
|
||||||
// addressbook included in MIGRATE_BASE above
|
|
||||||
"keyBackup,logs,netDb,peerProfiles," +
|
|
||||||
// post install - files
|
|
||||||
// not required to copy - prngseed.rnd
|
|
||||||
// logger.config and router.config included in MIGRATE_BASE above
|
|
||||||
"bob.config,privatehosts.txt,router.info,router.keys," +
|
|
||||||
"sam.keys,susimail.config,userhosts.txt,webapps.config," +
|
|
||||||
"wrapper.log,wrapper.log.1,wrapper.log.2";
|
|
||||||
|
|
||||||
private static boolean migrate(String list, File olddir, File todir) {
|
private static boolean migrate(String list, File olddir, File todir) {
|
||||||
boolean rv = true;
|
boolean rv = true;
|
||||||
String files[] = list.split(",");
|
String files[] = list.split(",");
|
||||||
@ -208,132 +165,11 @@ public class WorkingDir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy over the i2psnark.config file with modifications
|
* Copy over the clients.config file with modifications
|
||||||
*/
|
*/
|
||||||
private static boolean migrateSnark(File olddir, File todir) {
|
private static boolean migrateClientsConfig(File olddir, File todir) {
|
||||||
boolean rv = true;
|
File oldFile = new File(olddir, "clients.config");
|
||||||
File oldSnark = new File(olddir, "i2psnark");
|
File newFile = new File(todir, "clients.config");
|
||||||
File newSnark = new File(todir, "i2psnark");
|
|
||||||
File oldSnarkConfig = new File(olddir, "i2psnark.config");
|
|
||||||
File newSnarkConfig = new File(todir, "i2psnark.config");
|
|
||||||
boolean hasData = oldSnark.exists();
|
|
||||||
if (hasData) {
|
|
||||||
File children[] = oldSnark.listFiles();
|
|
||||||
hasData = children != null && children.length > 0;
|
|
||||||
}
|
|
||||||
if (oldSnarkConfig.exists()) {
|
|
||||||
if (hasData) {
|
|
||||||
// edit the snark config file to point to the old location, we aren't moving the data
|
|
||||||
try {
|
|
||||||
Properties props = new Properties();
|
|
||||||
DataHelper.loadProps(props, oldSnarkConfig);
|
|
||||||
String dir = props.getProperty("i2psnark.dir");
|
|
||||||
if (dir == null)
|
|
||||||
dir = "i2psnark";
|
|
||||||
// change relative to absolute path
|
|
||||||
File f = new File(dir);
|
|
||||||
props.setProperty("i2psnark.dir", f.getAbsolutePath());
|
|
||||||
DataHelper.storeProps(props, newSnarkConfig);
|
|
||||||
System.err.println("Copied i2psnark.config with modifications");
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
System.err.println("FAILED copy i2psnark.config");
|
|
||||||
rv = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// copy the i2psnark config file over
|
|
||||||
copy(newSnarkConfig, todir);
|
|
||||||
System.err.println("Copied i2psnark.config");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (hasData) {
|
|
||||||
// data but no previous config file (unlikely) - make new config file
|
|
||||||
try {
|
|
||||||
Properties props = new Properties();
|
|
||||||
File f = new File("i2psnark");
|
|
||||||
props.setProperty("i2psnark.dir", f.getAbsolutePath());
|
|
||||||
DataHelper.storeProps(props, newSnarkConfig);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
} // else no config and no data
|
|
||||||
}
|
|
||||||
if (hasData) {
|
|
||||||
/*************
|
|
||||||
// crude attempt to detect same filesystem
|
|
||||||
if ((oldSnark.getAbsolutePath().startsWith("/home/") && newSnark.getAbsolutePath().startsWith("/home/")) ||
|
|
||||||
(System.getProperty("os.name").toLowerCase.indexOf("windows") >= 0 &&
|
|
||||||
oldSnark.getAbsolutePath().substring(0,1).equals(newSnark.getAbsolutePath().substring(0,1) &&
|
|
||||||
oldSnark.getAbsolutePath().substring(1,2).equals(":\\") &&
|
|
||||||
newSnark.getAbsolutePath().substring(1,2).equals(":\\"))) {
|
|
||||||
// OK, apparently in same file system
|
|
||||||
// move everything
|
|
||||||
}
|
|
||||||
**************/
|
|
||||||
System.err.println("NOT moving the i2psnark data directory " + oldSnark.getAbsolutePath() +
|
|
||||||
" to the new directory " + newSnark.getAbsolutePath() +
|
|
||||||
". You may move the directory contents manually WHILE I2P IS NOT RUNNING," +
|
|
||||||
" and edit the file " + newSnarkConfig.getAbsolutePath() +
|
|
||||||
" to configure i2psnark to use a different location by editing the i2psnark.dir configuration to be" +
|
|
||||||
" i2psnark.dir=" + oldSnark.getAbsolutePath() +
|
|
||||||
" and restart, or you may leave the i2psnark directory in its old location.");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy over the i2prouter file with modifications
|
|
||||||
* The resulting script can be run from any location.
|
|
||||||
*/
|
|
||||||
private static boolean migrateI2prouter(File olddir, File todir) {
|
|
||||||
File oldFile = new File(olddir, "i2prouter");
|
|
||||||
File newFile = new File(todir, "i2prouter");
|
|
||||||
FileInputStream in = null;
|
|
||||||
PrintWriter out = null;
|
|
||||||
try {
|
|
||||||
in = new FileInputStream(oldFile);
|
|
||||||
out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
|
|
||||||
boolean firstTime = true;
|
|
||||||
String s = null;
|
|
||||||
while ((s = DataHelper.readLine(in)) != null) {
|
|
||||||
if (s.equals("WRAPPER_CMD=\"./i2psvc\"")) {
|
|
||||||
// i2psvc in the old location
|
|
||||||
File f = new File("i2psvc");
|
|
||||||
s = "WRAPPER_CMD=\"" + f.getAbsolutePath() + "\"";
|
|
||||||
} else if(s.equals("WRAPPER_CONF=\"wrapper.config\"")) {
|
|
||||||
// wrapper.config the new location
|
|
||||||
File f = new File(todir, "wrapper.config");
|
|
||||||
s = "WRAPPER_CONF=\"" + f.getAbsolutePath() + "\"";
|
|
||||||
} else if(s.equals("PIDDIR=\".\"")) {
|
|
||||||
// i2p.pid in the new location
|
|
||||||
s = "PIDDIR=\"" + todir.getAbsolutePath() + "\"";
|
|
||||||
}
|
|
||||||
out.println(s);
|
|
||||||
if (firstTime) {
|
|
||||||
// first line was #!/bin/sh, so had to wait until now
|
|
||||||
out.println("# Modified by I2P User dir migration script");
|
|
||||||
firstTime = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
System.err.println("Copied i2prouter with modifications");
|
|
||||||
return true;
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
if (in != null) {
|
|
||||||
System.err.println("FAILED copy i2prouter");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
|
||||||
if (out != null) out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy over the wrapper.config file with modifications
|
|
||||||
*/
|
|
||||||
private static boolean migrateWrapperConfig(File olddir, File todir) {
|
|
||||||
File oldFile = new File(olddir, "wrapper.config");
|
|
||||||
File newFile = new File(todir, "wrapper.config");
|
|
||||||
FileInputStream in = null;
|
FileInputStream in = null;
|
||||||
PrintWriter out = null;
|
PrintWriter out = null;
|
||||||
try {
|
try {
|
||||||
@ -341,36 +177,17 @@ public class WorkingDir {
|
|||||||
out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
|
out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
|
||||||
out.println("# Modified by I2P User dir migration script");
|
out.println("# Modified by I2P User dir migration script");
|
||||||
String s = null;
|
String s = null;
|
||||||
// Don't use replaceFirst because backslashes in the replacement string leads to havoc
|
|
||||||
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4689750
|
|
||||||
// "Note that backslashes and dollar signs in the replacement string may cause the results
|
|
||||||
// to be different than if it were being treated as a literal replacement string.
|
|
||||||
// Dollar signs may be treated as references to captured subsequences as described above,
|
|
||||||
// and backslashes are used to escape literal characters in the replacement string."
|
|
||||||
while ((s = DataHelper.readLine(in)) != null) {
|
while ((s = DataHelper.readLine(in)) != null) {
|
||||||
if (s.startsWith("wrapper.java.classpath.")) {
|
if (s.endsWith("=eepsite/jetty.xml")) {
|
||||||
// libraries in the old location
|
s = s.replace("=eepsite", '=' + todir.getAbsolutePath() + File.separatorChar + "eepsite");
|
||||||
s = s.replace("=lib/", '=' + olddir.getAbsolutePath() + File.separatorChar + "lib" + File.separatorChar);
|
|
||||||
} else if (s.startsWith("wrapper.java.library.path.")) {
|
|
||||||
// libraries in the old location
|
|
||||||
if (s.contains("=."))
|
|
||||||
s = s.replace("=.", '=' + olddir.getAbsolutePath());
|
|
||||||
else if (s.contains("=lib"))
|
|
||||||
s = s.replace("=lib", '=' + olddir.getAbsolutePath() + File.separatorChar + "lib");
|
|
||||||
} else if (s.startsWith("wrapper.logfile=wrapper.log")) {
|
|
||||||
// wrapper logs in the new location
|
|
||||||
s = s.replace("=", '=' + todir.getAbsolutePath() + File.separatorChar);
|
|
||||||
} else if (s.startsWith("wrapper.pidfile=i2p.pid")) {
|
|
||||||
// i2p.pid in the new location
|
|
||||||
s = s.replace("=", '=' + todir.getAbsolutePath() + File.separatorChar);
|
|
||||||
}
|
}
|
||||||
out.println(s);
|
out.println(s);
|
||||||
}
|
}
|
||||||
System.err.println("Copied wrapper.config with modifications");
|
System.err.println("Copied clients.config with modifications");
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
if (in != null) {
|
if (in != null) {
|
||||||
System.err.println("FAILED copy wrapper.config");
|
System.err.println("FAILED copy clients.config");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -417,16 +234,6 @@ public class WorkingDir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Relatively recent default i2ptunnel key file name
|
|
||||||
*/
|
|
||||||
private static boolean migrateI2PTunnelKeys(File olddir, File todir) {
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
copy(new File(olddir, "i2ptunnel" + i + "-privKeys.dat"), todir);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursive copy a file or dir to a dir
|
* Recursive copy a file or dir to a dir
|
||||||
*
|
*
|
||||||
@ -442,6 +249,7 @@ public class WorkingDir {
|
|||||||
System.err.println("FAILED copy " + src.getPath());
|
System.err.println("FAILED copy " + src.getPath());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
System.err.println("Created " + targetDir.getPath());
|
||||||
}
|
}
|
||||||
File targetFile = new File(targetDir, src.getName());
|
File targetFile = new File(targetDir, src.getName());
|
||||||
if (!src.isDirectory())
|
if (!src.isDirectory())
|
||||||
@ -451,6 +259,14 @@ public class WorkingDir {
|
|||||||
System.err.println("FAILED copy " + src.getPath());
|
System.err.println("FAILED copy " + src.getPath());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// make it here so even empty dirs get copied
|
||||||
|
if (!targetFile.exists()) {
|
||||||
|
if (!targetFile.mkdir()) {
|
||||||
|
System.err.println("FAILED copy " + src.getPath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
System.err.println("Created " + targetFile.getPath());
|
||||||
|
}
|
||||||
boolean rv = true;
|
boolean rv = true;
|
||||||
for (int i = 0; i < children.length; i++) {
|
for (int i = 0; i < children.length; i++) {
|
||||||
rv &= copy(children[i], targetFile);
|
rv &= copy(children[i], targetFile);
|
||||||
|
Reference in New Issue
Block a user