OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 142 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
61 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
61 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils.sync;
15
 
16
import org.openconcerto.utils.Base64;
17
import org.openconcerto.utils.FileUtils;
18
 
19
import java.io.BufferedInputStream;
20
import java.io.BufferedOutputStream;
182 ilm 21
import java.io.ByteArrayInputStream;
61 ilm 22
import java.io.ByteArrayOutputStream;
23
import java.io.DataInputStream;
24
import java.io.DataOutputStream;
25
import java.io.File;
26
import java.io.FileInputStream;
27
import java.io.FileNotFoundException;
28
import java.io.FileOutputStream;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.OutputStreamWriter;
32
import java.io.RandomAccessFile;
33
import java.io.UnsupportedEncodingException;
34
import java.net.MalformedURLException;
35
import java.net.URL;
36
import java.net.URLConnection;
37
import java.net.URLEncoder;
38
import java.security.MessageDigest;
39
import java.text.SimpleDateFormat;
40
import java.util.ArrayList;
41
import java.util.Collections;
42
import java.util.Date;
43
import java.util.HashMap;
44
import java.util.List;
45
import java.util.Random;
46
import java.util.zip.GZIPInputStream;
47
import java.util.zip.GZIPOutputStream;
48
 
49
import javax.net.ssl.HostnameVerifier;
50
import javax.net.ssl.HttpsURLConnection;
51
import javax.net.ssl.SSLSession;
52
 
53
public class SyncClient {
54
    private long byteSent;
55
    private long byteReceived;
56
    private long byteSyncDownload;
57
    private long byteSyncUpload;
58
    private long filesSyncDownload;
59
    private long filesSyncUpload;
60
    private String baseUrl = "http://127.0.0.1:80";
61
    private boolean verifyHost = true;
62
 
63
    /**
64
     * @param args
65
     * @throws Exception
66
     */
67
    public static void main(String[] args) throws Exception {
68
        if (args.length < 2) {
69
            printUsage();
70
            return;
71
        }
72
        String token = "";
73
        SyncClient c = new SyncClient();
74
        final String command = args[0];
75
        if (command.equalsIgnoreCase("put")) {
76
            String from = args[1];
77
            String to = args[2];
78
            String remotePath = c.setServerUrlFrom(to);
79
            if (remotePath != null) {
80
                final File localFile = new File(from);
81
                if (!localFile.exists()) {
82
                    System.out.println(localFile.getAbsolutePath() + " does not exist");
83
                    return;
84
                }
85
                if (!localFile.isDirectory()) {
86
                    String remoteName;
87
                    if (to.endsWith("/")) {
88
                        remoteName = localFile.getName();
89
                    } else {
90
                        remoteName = to.substring(to.lastIndexOf("/") + 1);
91
                    }
92
                    c.sendFile(localFile, remotePath, remoteName);
93
                } else {
94
                    c.sendDirectory(localFile, remotePath);
95
                }
96
                c.dumpStat();
97
            }
98
            return;
99
        } else if (command.equalsIgnoreCase("get")) {
100
            String rPath = c.setServerUrlFrom(args[1]);
101
            String to = args[2];
102
 
103
            final File dir = new File(to);
104
            if (dir.isFile()) {
105
                System.out.println(dir.getAbsolutePath() + " is not a directory");
106
                return;
107
            }
108
            dir.mkdirs();
109
            if (rPath.endsWith("/")) {
110
 
111
                c.retrieveDirectory(dir, rPath, token);
112
                c.dumpStat();
113
            } else {
114
                String remoteName = rPath;
115
                String remotePath = "/";
116
                if (rPath.contains("/")) {
117
                    int i = rPath.lastIndexOf('/');
118
                    remotePath = rPath.substring(0, i);
119
                    remoteName = rPath.substring(i + 1);
120
                }
121
                try {
122
                    c.retrieveFile(dir, remotePath, remoteName, token);
123
                    c.dumpStat();
124
                } catch (FileNotFoundException e) {
125
                    System.out.println("The file you are trying to download does not exists. If you are trying to download a directory, just add a / ");
126
 
127
                }
128
            }
129
            return;
130
        } else if (command.equalsIgnoreCase("list")) {
131
            String url = args[1];
132
            if (!url.startsWith("http://")) {
133
                System.out.println(url + " is not an http url, exiting.");
134
                return;
135
            }
136
            int n = url.substring(7).indexOf('/');
137
            if (n <= 2) {
138
                System.out.println("invalid url " + url);
139
                return;
140
            }
141
            c.baseUrl = url.substring(0, 7 + n);
142
            final String remothPath = url.substring(7 + n);
143
            try {
144
                c.listDirectory(remothPath, token);
145
            } catch (Exception e) {
146
                System.out.println(remothPath + " does not exist on the server");
147
            }
148
            return;
149
        } else if (command.equalsIgnoreCase("del") || command.equalsIgnoreCase("rm") || command.equalsIgnoreCase("delete") || command.equalsIgnoreCase("remove")) {
150
            System.out.println("My coder said I'm not allowed to delete files today. Sorry.");
151
            return;
152
        } else {
153
            System.out.println("Only commands put, get and list are allowed.");
154
            printUsage();
155
            return;
156
        }
157
 
158
    }
159
 
160
    private void sendDirectory(File localDir, String remotePath) throws Exception {
161
        if (!remotePath.endsWith("/")) {
162
            remotePath += "/";
163
        }
164
        File[] fl = localDir.listFiles();
165
        for (int i = 0; i < fl.length; i++) {
166
            File file = fl[i];
167
            if (file.isDirectory()) {
168
                sendDirectory(file, remotePath + file.getName());
169
            } else {
170
                sendFile(file, remotePath, file.getName());
171
            }
172
        }
173
 
174
    }
175
 
176
    public SyncClient() {
177
    }
178
 
179
    public SyncClient(String baseUrl) {
180
        this.baseUrl = baseUrl;
181
    }
182
 
183
    private String setServerUrlFrom(String url) {
184
        if (!url.startsWith("http://") || !url.startsWith("https://")) {
185
            System.out.println(url + " is not an http url, exiting.");
186
            return null;
187
        }
188
        int n = url.substring(7).indexOf('/');
189
        if (n <= 2) {
190
            System.out.println("invalid url " + url);
191
            return null;
192
        }
193
        baseUrl = url.substring(0, 7 + n);
194
        final String remothPath = url.substring(7 + n);
195
        return remothPath;
196
    }
197
 
198
    private static void printUsage() {
199
        System.out.println("SyncClient usage examples:");
200
        System.out.println("Upload");
201
        System.out.println("To upload a file:           java -jar sync.jar put  /etc/fstab http://16.105.9.190:80/backup/fstabOk");
202
        System.out.println("To upload a directory:      java -jar sync.jar put  /etc/      http://16.105.9.190:80/backup/etc/");
203
        System.out.println("Download");
204
        System.out.println("To download a file:         java -jar sync.jar get  http://16.105.9.190:80/backup/fstabOk /tmp/");
205
        System.out.println("To download a directory:    java -jar sync.jar get  http://16.105.9.190:80/backup/        /tmp/");
206
        System.out.println("To list a remote directory: java -jar sync.jar list http://16.105.9.190:80/backup/");
207
        System.out.println("Note: directory synchronization is recursive");
208
    }
209
 
210
    public void dumpStat() {
211
        System.out.println("SyncClient statistics");
212
        System.out.println("Download:");
213
        System.out.println("- files received: " + this.filesSyncDownload);
214
        System.out.println("- bytes sync'ed :" + this.byteSyncDownload);
215
        System.out.print("- bytes received: " + this.byteReceived);
216
        if (this.byteSyncDownload > 0) {
217
            System.out.println(" (" + ((100f * this.byteReceived) / this.byteSyncDownload) + "%)");
218
        } else {
219
            System.out.println();
220
        }
221
 
222
        System.out.println("Upload:");
223
        System.out.println("- files sent: " + this.filesSyncUpload);
224
        System.out.println("- bytes sync'ed :" + this.byteSyncUpload);
225
        System.out.print("- bytes sent: " + this.byteSent);
226
        if (this.byteSyncUpload > 0) {
227
            System.out.println(" (" + ((100f * this.byteSent) / this.byteSyncUpload) + "%)");
228
        } else {
229
            System.out.println();
230
        }
231
    }
232
 
233
    public void sendFile(File localFile, String remotePath, String remoteName) throws Exception {
234
        sendFile(localFile, remotePath, remoteName, null);
235
    }
236
 
237
    public void sendFile(File localFile, String remotePath, String remoteName, String token) throws Exception {
238
        if (localFile == null) {
239
            throw new IllegalArgumentException("null file");
240
        }
241
        if (!localFile.exists()) {
242
            throw new IllegalArgumentException(localFile.getAbsolutePath() + " does not exist");
243
        }
244
        this.filesSyncUpload++;
245
 
246
        // Construct data
247
        String data = URLEncoder.encode("rp", "UTF-8") + "=" + URLEncoder.encode(remotePath, "UTF-8");
248
        data += "&" + URLEncoder.encode("rn", "UTF-8") + "=" + URLEncoder.encode(remoteName, "UTF-8");
249
        if (token != null) {
250
            data += "&" + URLEncoder.encode("tk", "UTF-8") + "=" + URLEncoder.encode(token, "UTF-8");
251
        }
252
        this.byteSent += data.getBytes().length;
80 ilm 253
        // Send the path, file name and token
61 ilm 254
        URLConnection conn = getConnection(this.baseUrl + "/getHash");
255
        conn.setDoOutput(true);
256
        OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
257
        wr.write(data);
258
        wr.flush();
259
        wr.close();
80 ilm 260
        // Get the info about the remote file
61 ilm 261
        byte[] localFileHash = null;
262
        int localFileSize = (int) localFile.length();
263
        RangeList rangesOk = new RangeList(localFileSize);
264
        MoveOperationList moves = new MoveOperationList();
265
        this.byteSyncUpload = localFileSize;
266
        try {
80 ilm 267
            // Process the response
61 ilm 268
            DataInputStream in = new DataInputStream(new BufferedInputStream(conn.getInputStream()));
80 ilm 269
 
270
            // Remote file size
61 ilm 271
            int remoteFileSize = in.readInt();
272
            this.byteReceived += 4;
273
 
93 ilm 274
            final float a = (float) remoteFileSize / HashWriter.BLOCK_SIZE;
61 ilm 275
 
276
            int nb = (int) Math.ceil(a);
277
 
278
            HashMap<Integer, byte[]> map = new HashMap<Integer, byte[]>(nb * 2 + 1);
279
            HashMap<Integer, Integer> mapBlock = new HashMap<Integer, Integer>(nb * 2 + 1);
280
            for (int i = 0; i < nb; i++) {
281
                byte[] b = new byte[16];
282
                final int r32 = in.readInt();
283
                // System.out.print("Known hash " + r32 + " : ");
284
                in.read(b);
285
                this.byteReceived += 4 + 16;
80 ilm 286
                // MD5 hash
61 ilm 287
                map.put(r32, b);
80 ilm 288
                //
61 ilm 289
                mapBlock.put(r32, i);
290
            }
80 ilm 291
            // The hash (SHA256) of the remote file
61 ilm 292
            byte[] remoteFileHash = new byte[32];
293
            in.read(remoteFileHash);
294
            this.byteReceived += 32;
295
            in.close();
296
 
297
            if (localFileSize == remoteFileSize) {
298
                localFileHash = HashWriter.getHash(localFile);
299
                if (HashWriter.compareHash(localFileHash, remoteFileHash)) {
300
                    // Already in sync
301
                    return;
302
                }
303
            }
304
 
80 ilm 305
            if (localFileSize > 0) {
61 ilm 306
 
80 ilm 307
                // compare delta
308
                RollingChecksum32 checksum = new RollingChecksum32();
93 ilm 309
                byte[] buffer = new byte[HashWriter.BLOCK_SIZE];
61 ilm 310
 
80 ilm 311
                BufferedInputStream fb = new BufferedInputStream(new FileInputStream(localFile));
312
                final int read = fb.read(buffer);
313
                this.byteReceived += read;
314
                checksum.check(buffer, 0, read);
315
                int v = 0;
316
                int start = 0;
61 ilm 317
 
80 ilm 318
                MessageDigest md5Digest = MessageDigest.getInstance("MD5");
61 ilm 319
 
80 ilm 320
                do {
61 ilm 321
 
80 ilm 322
                    int r32 = checksum.getValue();
61 ilm 323
 
80 ilm 324
                    byte[] md5 = map.get(r32);
325
                    if (md5 != null) {
326
                        // local block maybe exists in the remote file
327
                        // let's check if true with md5
328
                        md5Digest.reset();
329
                        md5Digest.update(buffer);
330
                        byte[] localMd5 = md5Digest.digest();
331
                        if (HashWriter.compareHash(md5, localMd5)) {
61 ilm 332
 
80 ilm 333
                            // Block found!!!
334
                            // Copy block to: mapBlock.get(r32)*blockSize;
93 ilm 335
                            int offset = mapBlock.get(r32) * HashWriter.BLOCK_SIZE;
80 ilm 336
                            //
93 ilm 337
                            MoveOperation m = new MoveOperation(offset, start, HashWriter.BLOCK_SIZE);
80 ilm 338
                            moves.add(m);
339
                            //
93 ilm 340
                            rangesOk.add(new Range(start, start + HashWriter.BLOCK_SIZE));
80 ilm 341
                        }
61 ilm 342
 
80 ilm 343
                    }
61 ilm 344
 
80 ilm 345
                    // read
346
                    v = fb.read();
61 ilm 347
 
80 ilm 348
                    start++;
349
                    // Update
350
                    System.arraycopy(buffer, 1, buffer, 0, buffer.length - 1);
351
                    buffer[buffer.length - 1] = (byte) v;
352
                    checksum.roll((byte) v);
61 ilm 353
 
80 ilm 354
                } while (v >= 0);
355
                fb.close();
356
            }
61 ilm 357
        } catch (FileNotFoundException e) {
358
            // System.out.println("Sending the complete file");
359
        }
360
        if (localFileHash == null) {
361
            localFileHash = HashWriter.getHash(localFile);
362
        }
73 ilm 363
        try {
364
            sendDelta(localFile, remotePath, remoteName, moves, rangesOk.getUnusedRanges(), localFileHash, token);
365
        } catch (Exception e) {
132 ilm 366
            // Sending by delta failed, fallback to sendind the complete file
367
            System.err.println("SyncClient.sendFile() Unable to send delta: " + localFile.getAbsolutePath() + " to " + remoteName + " " + moves);
73 ilm 368
            rangesOk.dump();
132 ilm 369
            try {
370
                System.err.println("SyncClient.sendFile() sending complete file");
371
                sendCompleteFile(localFile, remotePath, remoteName, token);
372
            } catch (Exception e2) {
373
                System.err.println("SyncClient.sendFile() Unable to send complete file");
374
                throw e2;
375
            }
376
 
73 ilm 377
        }
61 ilm 378
    }
379
 
132 ilm 380
    private void sendCompleteFile(File localFile, String remotePath, String remoteName, String token) throws Exception {
381
        byte[] localFileHash = HashWriter.getHash(localFile);
382
        MoveOperationList noMoves = new MoveOperationList();
383
        List<Range> rangesToSend = new ArrayList<Range>(1);
384
        rangesToSend.add(new Range(0, (int) localFile.length()));
385
        sendDelta(localFile, remotePath, remoteName, noMoves, rangesToSend, localFileHash, token);
386
 
387
    }
388
 
61 ilm 389
    private void sendDelta(File localFile, String remotePath, String remoteName, MoveOperationList moves, List<Range> rangesToSend, byte[] localFileHash, String token) throws IOException {
390
        if (token == null) {
391
            token = "";
392
        }
393
 
394
        URLConnection conn = getConnection(this.baseUrl + "/putFile");
395
        conn.setDoOutput(true);
396
 
397
        final DataOutputStream wr = new DataOutputStream(new GZIPOutputStream(new BufferedOutputStream(conn.getOutputStream())) {
398
            @Override
399
            public synchronized void write(byte[] b, int off, int len) throws IOException {
400
                SyncClient.this.byteSent += len;
401
                super.write(b, off, len);
402
            }
403
 
404
            @Override
405
            public synchronized void write(int b) throws IOException {
406
                SyncClient.this.byteSent++;
407
                super.write(b);
408
            }
409
        });
410
 
411
        wr.writeUTF(remotePath);
412
        wr.writeUTF(remoteName);
413
        wr.writeUTF(token);
414
        wr.write(localFileHash);
415
        wr.writeInt((int) localFile.length());
416
        // Moves
417
        moves.write(wr);
418
 
419
        // Delta
420
        wr.writeInt(rangesToSend.size());
421
        RandomAccessFile rIn = new RandomAccessFile(localFile, "r");
422
 
423
        for (Range r : rangesToSend) {
424
            rIn.seek(r.getStart());
425
            byte[] buffer = new byte[r.getStop() - r.getStart()];
426
            rIn.readFully(buffer);
427
 
428
            wr.writeInt(r.getStart());
429
            wr.writeInt(r.getStop());
430
            wr.write(buffer);
431
            // System.out.println("SyncClient.sendDelta() : " + r.getStart() + "-" + r.getStop() +
432
            // " " + buffer.length);
433
 
434
        }
435
        rIn.close();
436
        wr.flush();
437
        wr.close();
438
 
439
        // Reading data is mandatory
440
        DataInputStream in = new DataInputStream(new BufferedInputStream(conn.getInputStream()));
441
        this.byteReceived += 32;
442
        byte[] rHash = new byte[32];
443
        in.read(rHash);
444
        in.close();
445
        if (!HashWriter.compareHash(localFileHash, rHash)) {
446
            throw new IllegalStateException("Hash error");
447
        }
448
 
449
    }
450
 
451
    public void retrieveDirectory(File dir, String remotePath, String token) throws Exception {
80 ilm 452
 
453
        ArrayList<FileProperty> list = null;
454
        try {
455
            list = getList(remotePath, token);
456
        } catch (Exception e) {
457
            throw new IllegalStateException("Unable to retrieve the file list of " + remotePath, e);
458
        }
61 ilm 459
        // Check locally
460
        for (int i = 0; i < list.size(); i++) {
461
            final FileProperty fp = list.get(i);
462
            if (!fp.isDirectory()) {
463
                retrieveFile(dir, remotePath, fp.getName(), fp.getSize(), fp.getSha256(), token);
464
            } else {
465
                final File dir2 = new File(dir, fp.getName());
466
                dir2.mkdirs();
467
                retrieveDirectory(dir2, remotePath + "/" + fp.getName(), token);
468
            }
469
        }
470
 
471
    }
472
 
473
    public void listDirectory(String remotePath, String token) throws Exception {
474
        ArrayList<FileProperty> list = getList(remotePath, token);
475
        Collections.sort(list);
476
        final int size = list.size();
477
        int maxSize = -1;
478
        for (int i = 0; i < size; i++) {
479
            int s = list.get(i).getSize();
480
            if (s > maxSize) {
481
                maxSize = s;
482
            }
483
        }
484
        int lSize = (" " + maxSize).length();
485
        if (maxSize < 0) {
486
            lSize = 0;
487
        }
488
        SimpleDateFormat spd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
489
        for (int i = 0; i < size; i++) {
490
            FileProperty fp = list.get(i);
491
            if (lSize > 0) {
492
                if (fp.getSize() >= 0) {
493
                    System.out.print(rightAlign(String.valueOf(fp.getSize()), lSize));
494
                } else {
495
                    System.out.print(rightAlign("", lSize));
496
                }
497
                System.out.print(" " + spd.format(fp.getDate()));
498
                System.out.println(" " + fp.getName());
499
            } else {
500
                System.out.println(fp.getName());
501
            }
502
 
503
        }
504
    }
505
 
506
    public static String rightAlign(String s, int width) {
507
        String r = s;
508
        int n = width - s.length();
509
        for (int i = 0; i < n; i++) {
510
            r = ' ' + r;
511
        }
512
        return r;
513
    }
514
 
515
    public ArrayList<FileProperty> getList(String remotePath, String token) throws UnsupportedEncodingException, MalformedURLException, IOException {
516
        // If the file exists or not, get the hashValues to compare the hash
517
 
518
        // Construct data
519
        String data = "rp=" + URLEncoder.encode(remotePath, "UTF-8");
520
        if (token != null) {
521
            data += "&tk=" + URLEncoder.encode(token, "UTF-8");
522
        }
523
        this.byteSent += data.getBytes().length;
524
        // Send data
525
        URLConnection conn = getConnection(baseUrl + "/getDir");
526
        conn.setDoOutput(true);
527
        OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
528
        wr.write(data);
529
        wr.flush();
530
        wr.close();
531
 
532
        // Get the response ASAP in order to not block the server while computing locally hash256
182 ilm 533
 
534
        // Copy the entire data first because DataInputStream will fail on partial buffer (mainly
535
        // readUTF)
536
        InputStream ins = conn.getInputStream();
537
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
538
        StreamUtils.copy(ins, out);
539
        ins.close();
540
 
541
        DataInputStream in = new DataInputStream(new ByteArrayInputStream(out.toByteArray()));
542
 
61 ilm 543
        int fileCount = in.readInt();
544
        this.byteReceived += 4;
545
        ArrayList<FileProperty> list = new ArrayList<FileProperty>();
546
        for (int i = 0; i < fileCount; i++) {
547
            final String fileName = in.readUTF();
548
            this.byteReceived += fileName.getBytes().length;
549
            final int fileSize = in.readInt();
550
            this.byteReceived += 4;
551
            final long fileDate = in.readLong();
552
            this.byteReceived += 8;
553
            final byte[] sha256 = new byte[32];
554
            if (fileSize >= 0) {
555
                in.read(sha256);
556
                this.byteReceived += 32;
557
            }
558
            FileProperty fp = new FileProperty(fileName, fileSize, fileDate, sha256);
559
            list.add(fp);
560
        }
561
        in.close();
562
        return list;
563
    }
564
 
565
    /**
566
     * @return true if the file exists and is retrieved :)
142 ilm 567
     */
61 ilm 568
    public void retrieveFile(File dir, String remotePath, String remoteName, String token) throws Exception {
569
 
570
        // If the file exists or not, get the hashValues to compare the hash
571
 
572
        // Construct data
573
        String data = "rp=" + URLEncoder.encode(remotePath, "UTF-8");
574
        data += "&rn=" + URLEncoder.encode(remoteName, "UTF-8");
575
        data += "&shaOnly=" + URLEncoder.encode("true", "UTF-8");
576
        if (token != null) {
577
            data += "&tk=" + URLEncoder.encode(token, "UTF-8");
578
        }
579
        this.byteSent += data.getBytes().length;
580
        // Send data
581
        URLConnection conn = getConnection(this.baseUrl + "/getHash");
582
        conn.setDoOutput(true);
583
        OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
584
        wr.write(data);
585
        wr.flush();
586
        wr.close();
587
 
588
        // Get the response
589
 
590
        DataInputStream in = new DataInputStream(new BufferedInputStream(conn.getInputStream()));
591
        int fileSize = in.readInt();
592
        this.byteReceived += 4;
593
 
594
        byte[] fileHash = new byte[32];
595
        in.read(fileHash);
596
        this.byteReceived += 32;
597
        in.close();
598
 
599
        retrieveFile(dir, remotePath, remoteName, fileSize, fileHash, token);
600
 
601
    }
602
 
603
    private void retrieveFile(File dir, String remotePath, String remoteName, int fileSize, byte[] fileHash, String token) throws IOException, Exception {
604
        this.filesSyncDownload++;
605
        this.byteSyncDownload += fileSize;
606
        File localFile = resolveFile(dir, remoteName);
607
        if (!localFile.exists()) {
608
            downloadFile(localFile, remotePath, remoteName, fileSize, token);
609
            byte[] fileLocalHash = HashWriter.getHash(localFile);
610
            if (!HashWriter.compareHash(fileHash, fileLocalHash)) {
611
                throw new IllegalStateException("Full download failed. Hash error");
612
            }
613
        } else {
614
            boolean needToResync = false;
615
            // File exists
616
            // 1 - check size
617
            if (localFile.length() != fileSize) {
618
                needToResync = true;
619
            } else {
620
                // 2 - check hash sha256
621
                byte[] fileLocalHash = HashWriter.getHash(localFile);
622
                if (!HashWriter.compareHash(fileHash, fileLocalHash)) {
623
                    needToResync = true;
624
                }
625
            }
626
 
627
            if (needToResync) {
93 ilm 628
                if (localFile.length() > HashWriter.BLOCK_SIZE) {
142 ilm 629
                    try {
630
                        retrieveFileWithDelta(localFile, remotePath, remoteName, token);
631
                    } catch (Exception e) {
632
                        System.err.println("SyncClient.retrieveFile() failed for " + remotePath + " " + remoteName);
633
                        System.err.println("SyncClient.retrieveFile() fallback to plain download");
634
                        downloadFile(localFile, remotePath, remoteName, fileSize, token);
635
                        byte[] fileLocalHash = HashWriter.getHash(localFile);
636
                        if (!HashWriter.compareHash(fileHash, fileLocalHash)) {
637
                            throw new IllegalStateException("Full download failed. Hash error");
638
                        }
639
                    }
61 ilm 640
                } else {
641
                    downloadFile(localFile, remotePath, remoteName, fileSize, token);
642
                    byte[] fileLocalHash = HashWriter.getHash(localFile);
643
                    if (!HashWriter.compareHash(fileHash, fileLocalHash)) {
644
                        throw new IllegalStateException("Full download failed. Hash error");
645
                    }
646
                }
647
            }
648
 
649
        }
650
    }
651
 
652
    /**
653
     * @param fileHash2
654
     * @return true if the file exists and is retrieved :)
142 ilm 655
     */
61 ilm 656
    private void retrieveFileWithDelta(File localFile, String remotePath, String remoteName, String token) throws Exception {
657
 
658
        // Construct data
659
        String data = URLEncoder.encode("rp", "UTF-8") + "=" + URLEncoder.encode(remotePath, "UTF-8");
660
        data += "&" + URLEncoder.encode("rn", "UTF-8") + "=" + URLEncoder.encode(remoteName, "UTF-8");
661
        if (token != null) {
662
            data += "&tk=" + URLEncoder.encode(token, "UTF-8");
663
        }
664
        this.byteSent += data.getBytes().length;
665
        // Send data
666
        URLConnection conn = getConnection(this.baseUrl + "/getHash");
667
        conn.setDoOutput(true);
668
        OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
669
        wr.write(data);
670
        wr.flush();
671
        wr.close();
672
 
673
        // Get the response
674
        DataInputStream in = new DataInputStream(new BufferedInputStream(conn.getInputStream()));
675
        int fileSize = in.readInt();
676
        this.byteReceived += 4;
677
        this.byteSyncDownload = fileSize;
678
 
93 ilm 679
        final float a = (float) fileSize / HashWriter.BLOCK_SIZE;
61 ilm 680
 
681
        int nb = (int) Math.ceil(a);
682
 
683
        HashMap<Integer, byte[]> map = new HashMap<Integer, byte[]>(nb * 2 + 1);
684
        HashMap<Integer, Integer> mapBlock = new HashMap<Integer, Integer>(nb * 2 + 1);
685
        for (int i = 0; i < nb; i++) {
686
            byte[] b = new byte[16];
687
            final int r32 = in.readInt();
688
            // System.out.print("Known hash " + r32 + " : ");
689
            in.read(b);
690
            this.byteReceived += 4 + 16;
691
 
692
            map.put(r32, b);
693
            mapBlock.put(r32, i);
694
        }
695
        byte[] fileHash = new byte[32];
696
        in.read(fileHash);
697
        this.byteReceived += 32;
698
        in.close();
699
 
700
        // create the new file
701
        File newFile = createEmptyFile(localFile.getParentFile(), fileSize);
702
        RandomAccessFile rNewFile = new RandomAccessFile(newFile, "rw");
703
        // compare delta
704
        RollingChecksum32 checksum = new RollingChecksum32();
93 ilm 705
        byte[] buffer = new byte[HashWriter.BLOCK_SIZE];
61 ilm 706
 
707
        BufferedInputStream fb = new BufferedInputStream(new FileInputStream(localFile));
708
        final int read = fb.read(buffer);
709
        checksum.check(buffer, 0, read);
710
        int v = 0;
132 ilm 711
 
61 ilm 712
        MessageDigest md5Digest = MessageDigest.getInstance("MD5");
713
        RangeList rangesOk = new RangeList(fileSize);
714
        do {
715
            int r32 = checksum.getValue();
716
            byte[] md5 = map.get(r32);
717
            if (md5 != null) {
718
                // local block maybe exists in the remote file
719
                // let's check if true with md5
720
                md5Digest.reset();
721
                md5Digest.update(buffer);
722
                byte[] localMd5 = md5Digest.digest();
723
                if (HashWriter.compareHash(md5, localMd5)) {
724
                    // Block found!!!
725
                    // Copy block to: mapBlock.get(r32)*blockSize;
93 ilm 726
                    int offset = mapBlock.get(r32) * HashWriter.BLOCK_SIZE;
61 ilm 727
                    //
728
                    rNewFile.seek(offset);
729
                    rNewFile.write(buffer);
730
                    //
93 ilm 731
                    rangesOk.add(new Range(offset, offset + HashWriter.BLOCK_SIZE));
61 ilm 732
                }
733
            }
734
            // read
735
            v = fb.read();
736
            // Update
737
            System.arraycopy(buffer, 1, buffer, 0, buffer.length - 1);
738
            buffer[buffer.length - 1] = (byte) v;
739
            checksum.roll((byte) v);
740
        } while (v >= 0);
741
        fb.close();
742
        rangesOk.dump();
743
 
132 ilm 744
        // Download missing parts
61 ilm 745
        final List<Range> unusedRanges = rangesOk.getUnusedRanges();
746
        DataInputStream zIn = getContent(remotePath, remoteName, unusedRanges, token);
747
 
748
        BufferedOutputStream fOut = new BufferedOutputStream(new FileOutputStream(localFile));
749
 
750
        final int size = unusedRanges.size();
751
        for (int i = 0; i < size; i++) {
752
            Range range = unusedRanges.get(i);
753
            rNewFile.seek(range.getStart());
754
            byte[] b = new byte[(int) range.size()];
755
            zIn.readFully(b);
756
 
757
            rNewFile.write(b);
758
 
759
        }
760
 
761
        fOut.close();
762
        zIn.close();
763
 
764
        rNewFile.close();
765
 
766
        // Check hash sha256
767
        byte[] fileLocalHash = HashWriter.getHash(newFile);
768
        if (!HashWriter.compareHash(fileHash, fileLocalHash)) {
769
            throw new IllegalStateException("Partial download failed. Hash error");
770
        }
771
        FileUtils.rm(localFile);
772
        FileUtils.mv(newFile, localFile);
773
 
774
    }
775
 
776
    private File createEmptyFile(File dir, int fileSize) throws FileNotFoundException, IOException {
777
        Random r = new Random();
778
        File newFile = new File(dir, "sync." + r.nextInt());
779
        BufferedOutputStream bOut = new BufferedOutputStream(new FileOutputStream(newFile));
780
        int l = fileSize;
781
        byte[] emptyBuffer = new byte[4096];
782
        while (l > 0) {
783
            bOut.write(emptyBuffer, 0, Math.min(4096, (int) l));
784
            l -= 4096;
785
        }
786
        bOut.close();
787
        return newFile;
788
    }
789
 
790
    private void downloadFile(File localFile, String remotePath, String remoteName, int fileSize, String token) throws IOException {
791
        List<Range> list = new ArrayList<Range>(1);
792
        list.add(new Range(0, fileSize));
793
 
794
        DataInputStream zIn = getContent(remotePath, remoteName, list, token);
795
 
796
        BufferedOutputStream fOut = new BufferedOutputStream(new FileOutputStream(localFile));
797
        StreamUtils.copy(zIn, fOut);
798
        fOut.close();
799
        zIn.close();
800
 
801
    }
802
 
803
    private DataInputStream getContent(String remotePath, String remoteName, List<Range> list, String token) throws MalformedURLException, IOException, UnsupportedEncodingException {
804
        URLConnection conn = getConnection(this.baseUrl + "/getFile");
805
        conn.setDoOutput(true);
806
 
807
        final ByteArrayOutputStream bOutputStream = new ByteArrayOutputStream();
808
        final DataOutputStream wr = new DataOutputStream(bOutputStream);
809
        wr.writeInt(list.size());
810
        for (Range range : list) {
811
            // System.out.println("SyncClient.getContent() from server: " + range);
812
            wr.writeInt(range.getStart());
813
            wr.writeInt(range.getStop());
814
        }
815
        wr.close();
816
 
817
        String data = "rp=" + URLEncoder.encode(remotePath, "UTF-8");
818
        data += "&rn=" + URLEncoder.encode(remoteName, "UTF-8");
819
        data += "&ra=" + URLEncoder.encode(Base64.encodeBytes(bOutputStream.toByteArray()), "UTF-8");
820
        if (token != null) {
821
            data += "&tk=" + URLEncoder.encode(token, "UTF-8");
822
        }
823
        this.byteSent += data.getBytes().length;
824
        OutputStreamWriter osw = new OutputStreamWriter(conn.getOutputStream());
825
        osw.write(data);
826
        osw.flush();
827
        osw.close();
828
 
829
        final InputStream inputStream = new BufferedInputStream(conn.getInputStream()) {
830
            @Override
831
            public synchronized int read(byte[] b, int off, int len) throws IOException {
832
                byteReceived += len;
833
                return super.read(b, off, len);
834
            }
835
        };
836
        // System.out.println("SyncClient.downloadFile() " + inputStream.available());
837
        GZIPInputStream zIn = new GZIPInputStream(inputStream);
838
        return new DataInputStream(zIn);
839
    }
840
 
841
    List<Date> getVersions(String remotePath, String remoteName) {
842
        List<Date> l = new ArrayList<Date>();
843
        return l;
844
    }
845
 
846
    public File resolveFile(File dir, String remoteName) {
847
 
848
        if (remoteName.contains("..")) {
849
            return null;
850
        }
851
        if (remoteName.contains("/") || remoteName.contains("\\")) {
852
            return null;
853
        }
854
 
855
        return new File(dir, remoteName);
856
    }
857
 
858
    public void clearStat() {
859
        byteSent = 0;
860
        byteReceived = 0;
861
        byteSyncDownload = 0;
862
        byteSyncUpload = 0;
863
        filesSyncDownload = 0;
864
        filesSyncUpload = 0;
865
    }
866
 
867
    public static final HostnameVerifier HostnameNonVerifier = new HostnameVerifier() {
868
        @Override
869
        public boolean verify(String hostname, SSLSession session) {
870
            return true;
871
        }
872
    };
873
 
874
    URLConnection getConnection(String strUrl) throws IOException {
875
        URL url = new URL(strUrl);
876
        URLConnection conn = url.openConnection();
877
        if (!verifyHost && strUrl.startsWith("https")) {
878
            HttpsURLConnection httpsCon = (HttpsURLConnection) conn;
879
            httpsCon.setHostnameVerifier(HostnameNonVerifier);
880
        }
881
 
882
        return conn;
883
    }
884
 
885
    public void setVerifyHost(boolean verify) {
886
        this.verifyHost = verify;
887
    }
182 ilm 888
 
889
 
61 ilm 890
}