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(公園案内マップなど)がこれに含まれる。