Androidタブレット HAOVM M8 plus 上の地図アプリ Map も順調に動作するようになった。
スマホ(SP:Smart Phone)とタブレット(TC:Tablet Computer)を同時に使用するにあたり、ファイル同期を図りたい。
ファイルには以下の種類がある。
パソコン、タブレット、スマホ間のファイル転送は FTP を使う。スマホ、タブレットを FTPサーバーとするのは簡単であるが、 Windowsパソコンを FTPサーバー(IIS) とするのは、ちょっと面倒であるため、場合によってはレンタルサーバーを使う。 レンタルサーバーにホームページを置いており、FFFTP でミラーリングアップロードしている。
パソコン上の Common を FFFTP でレンタルサーバーにミラーリングアップロードする。 スマホとタブレットはレンタルサーバーからミラーリングダウンロードする。一手間増えるが、環境構築が簡単になる。
これまでは Mapディレクトリ下に全てのファイルを置いていたが、その下に大分類として、OSM、Common、GSI、SP、TC フォルダを置く。
OSM の原本はパソコンに置き、ファイル数の多いものはスマホおよびタブレットにzipファイルをFTP転送して、解凍する。
GSI はスマホおよびタブレットで個別管理とする。
Common の原本はパソコンに置き、ホームページと同様に、レンタルサーバーに FFFTP を使ってミラーリングアップロードする。 スマホおよびタブレットはレンタルサーバーからミラーリングダウンロードする。
TC にはタブレットコンピュータで生成されるファイルが置かれるが、スマホはこれをミラーリングダウンロードする。
SP にはスマホで生成されるファイルが置かれるが、タブレットはこれをミラーリングダウンロードする。
以上から、スマホおよびタブレットに必要なのはミラーリングダウンロードの機能のみである。
まずは、ミラーリングダウンロードのみとする。
サーバーからファイルリストを取り込んで、同名ファイルがローカルディレクトリになければ、無条件にダウンロードする。 ローカルに既に存在する場合は、時刻を比較して、ローカルとリモート(サーバー)が同じであれば、ダウンロードしない。 異なる場合は、ダウンロードする。
再帰処理を使えば、サブディレクトリにも対応できる。
できれば、将来、アップロードにも対応したいので、アップとダウンを区別する引数を用意した。今はまだ使っていない。
enum Command { mirroring_download, mirroring_upload }
public class FTPClientThread extends Thread {
ConstraintLayout layout;
Command cmd; // mirroring_download, mirroring_upload
String dirRemote;
String dirLocal;
public FTPClientThread(ConstraintLayout layout, Command cmd, String remote, String local) {
this.layout = layout;
this.cmd = cmd;
this.dirRemote = remote;
this.dirLocal = local;
}
@Override
public void run() {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
long start = System.currentTimeMillis();
FTPClient ftpCli = connectFTP(サーバー名, ポート番号, ユーザID, パスワード);
if (ftpCli == null) {
return;
}
try {
Path dirLocalObj = Paths.get(Map.DIR + dirLocal);
if (!Files.exists(dirLocalObj)) {
Files.createDirectories(dirLocalObj);
} // ディレクトリがなければ生成する
if (!ftpCli.changeWorkingDirectory(dirRemote)) {
throw new Exception("ディレクトリー移動エラー " + dirRemote +
" Code=" + ftpCli.getReplyCode());
}
HashSet<String> setRemote = new HashSet<>();
FTPFile[] ftpFiles = ftpCli.listFiles();
for (FTPFile file : ftpFiles) {
String name = file.getName();
if (name.startsWith(".")) continue;
setRemote.add(name);
long timeRemote = file.getTimestamp().getTimeInMillis();
if (file.getType() == FTPFile.DIRECTORY_TYPE) {
FTPClientThread thread = new FTPClientThread(layout, cmd,
dirRemote + "/" + name, dirLocal + "/" + name);
thread.start();
continue;
}
File fileObj = new File(Map.DIR + dirLocal + "/" + name);
Path pathObj = fileObj.toPath();
if (!Files.exists(pathObj)) {
Files.createFile(pathObj);
} else {
FileTime timeLocal = Files.getLastModifiedTime(pathObj);
if (timeRemote == timeLocal.toMillis()) {
//snackbarMake("変更なし " + name);
continue;
}
}
try (OutputStream outputStream
= new BufferedOutputStream(new FileOutputStream(fileObj))) {
// get the file from Ftp server and write it in outputStream.
ftpCli.retrieveFile(file.getName(), outputStream);
FileTime time = FileTime.fromMillis(timeRemote);
Files.setLastModifiedTime(pathObj, time);
}
FileTime time = FileTime.fromMillis(timeRemote);
Files.setLastModifiedTime(pathObj, time);
}
disconnectFTP(ftpCli);
File[] filesLocal = new File(Map.DIR + dirLocal).listFiles();
if (filesLocal != null) {
for (File file : filesLocal) {
String name = file.getName();
if (!setRemote.contains(name)) {
delete(new File(Map.DIR + dirLocal + "/" + name));
}
}
}
} catch (Exception e) {
snackbarMake(e.toString());
}
long elapsed = System.currentTimeMillis() - start;
snackbarMake(dirRemote + " " + (elapsed/1000) + "秒");
}
void delete(File file) throws Exception {
if (file.isFile()) {
Files.delete(file.toPath());
} else if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File file1 : files) {
delete(file1);
}
}
Files.delete(file.toPath());
}
}
FTPClient connectFTP(String sHost, int nPort, String sUser, String sPass) {
try {
FTPClient ftpCli = new FTPClient();
int nTimeout = 60 * 1000; //60秒
ftpCli.setDefaultTimeout(nTimeout);
ftpCli.setConnectTimeout(nTimeout); //タイムアウトをキャッチするにはこれが必要
ftpCli.connect(sHost, nPort);
if (!FTPReply.isPositiveCompletion(ftpCli.getReplyCode())) {
throw new Exception("FTP接続エラー Code=" + ftpCli.getReplyCode());
}
ftpCli.setSoTimeout(nTimeout);
if (!ftpCli.login(sUser, sPass)) {
throw new Exception("FTP認証エラー Code=" + ftpCli.getReplyCode());
}
//ファイル転送モード設定
//ftpCli.setFileType(FTP.ASCII_FILE_TYPE); //テキストファイルなど
ftpCli.setFileType(FTP.BINARY_FILE_TYPE); //画像ファイルなど
//モード設定
ftpCli.enterLocalPassiveMode(); //パッシブモード
//ftpCli.enterLocalActiveMode(); //アクティブモード
return ftpCli;
} catch (Exception e) {
snackbarMake(e.toString());
return null;
}
}
void disconnectFTP(FTPClient ftpCli) throws Exception {
if (ftpCli != null && ftpCli.isConnected()) {
ftpCli.disconnect();
}
}
private void snackbarMake(String message) {
Snackbar.make(layout, message, 10000).show();
}
}
まず、Common のダウンロードを試みた。当面、guides(バス時刻表)、img(地図アイコン)、infos(公園案内マップなど)がこれに含まれる。