MLSDComparison.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.net.ftp.parser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.TimeZone;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPFileFilters;
import org.apache.commons.net.ftp.FTPListParseEngine;
import org.junit.Test;

/**
 * Attempt comparison of LIST and MLSD listings
 *
 * TODO - needs some work.
 */
public class MLSDComparison {

    private final Comparator<FTPFile> cmp = (o1, o2) -> {
        final String n1 = o1.getName();
        final String n2 = o2.getName();
        return n1.compareTo(n2);
    };

    /**
     * Compare two instances to see if they are the same, ignoring any uninitialized fields.
     *
     * @param a first instance
     * @param b second instance
     * @return true if the initialized fields are the same
     * @since 3.0
     */
    public boolean areEquivalent(final FTPFile a, final FTPFile b) {
        return a.getName().equals(b.getName()) && areSame(a.getSize(), b.getSize(), -1L) &&
//            areSame(a.getUser(), b.getUser()) &&
//            areSame(a.getGroup(), b.getGroup()) &&
                areSame(a.getTimestamp(), b.getTimestamp())
//            areSame(a.getType(), b.getType(), UNKNOWN_TYPE) &&
//            areSame(a.getHardLinkCount(), b.getHardLinkCount(), 0) &&
//            areSame(a._permissions, b._permissions)
              ;
    }

    private boolean areSame(final Calendar a, final Calendar b) {
        return a == null || b == null || areSameDateTime(a, b);
    }

    private boolean areSame(final long a, final long b, final long d) {
        return a == d || b == d || a == b;
    }

    // compare permissions: default is all false, but that is also a possible
    // state, so this may miss some differences
//    private boolean areSame(boolean[][] a, boolean[][] b) {
//        return isDefault(a) || isDefault(b) || Arrays.deepEquals(a, b);
//    }

    // Is the array in its default state?
//    private boolean isDefault(boolean[][] a) {
//        for(boolean[] r : a){
//            for(boolean rc : r){
//                if (rc) { // not default
//                    return false;
//                }
//            }
//        }
//        return true;
//    }

    private boolean areSameDateTime(final Calendar a, final Calendar b) {
        final TimeZone UTC = TimeZone.getTimeZone("UTC");
        final Calendar ac = Calendar.getInstance(UTC);
        ac.setTime(a.getTime());
        final Calendar bc = Calendar.getInstance(UTC);
        bc.setTime(b.getTime());
        return isSameDay(ac, bc) && isSameTime(ac, bc);
    }

    private void compareSortedLists(final FTPFile[] lst, final FTPFile[] mlst) {
        Arrays.sort(lst, cmp);
        Arrays.sort(mlst, cmp);
        FTPFile first;
        FTPFile second;
        final int firstl = lst.length;
        final int secondl = mlst.length;
        int one = 0;
        int two = 0;
        first = lst[one++];
        second = mlst[two++];
        int cmp;
        while (one < firstl || two < secondl) {
//            String fs1 = first.toFormattedString();
//            String fs2 = second.toFormattedString();
            final String rl1 = first.getRawListing();
            final String rl2 = second.getRawListing();
            cmp = first.getName().compareTo(second.getName());
            if (cmp == 0) {
                if (first.getName().endsWith("HEADER.html")) {
                    cmp = 0;
                }
                if (!areEquivalent(first, second)) {
//                    System.out.println(rl1);
//                    System.out.println(fs1);
                    final long tdiff = first.getTimestamp().getTimeInMillis() - second.getTimestamp().getTimeInMillis();
                    System.out.println("Minutes diff " + tdiff / (1000 * 60));
//                    System.out.println(fs2);
//                    System.out.println(rl2);
//                    System.out.println();
//                    fail();
                }
                if (one < firstl) {
                    first = lst[one++];
                }
                if (two < secondl) {
                    second = mlst[two++];
                }
            } else if (cmp < 0) {
                if (!first.getName().startsWith(".")) { // skip hidden files
                    System.out.println("1: " + rl1);
                }
                if (one < firstl) {
                    first = lst[one++];
                }
            } else {
                System.out.println("2: " + rl2);
                if (two < secondl) {
                    second = mlst[two++];
                }
            }
        }
    }

    private boolean isSameDay(final Calendar a, final Calendar b) {
        final int ad = a.get(Calendar.DAY_OF_MONTH);
        final int bd = b.get(Calendar.DAY_OF_MONTH);
        return a.get(Calendar.YEAR) == b.get(Calendar.YEAR) && a.get(Calendar.MONTH) == b.get(Calendar.MONTH) && ad == bd;
    }

    private boolean isSameTime(final Calendar a, final Calendar b) {
        final int ah = a.get(Calendar.HOUR_OF_DAY);
        final int bh = b.get(Calendar.HOUR_OF_DAY);
        final int am = a.get(Calendar.MINUTE);
        final int bm = b.get(Calendar.MINUTE);
        final int as = a.get(Calendar.SECOND);
        final int bs = b.get(Calendar.SECOND);
        // @formatter:off
        return ah == 0 && am == 0 && as == 0
                || bh == 0 && bm == 0 && bs == 0
                || ah == bh && am == bm; // ignore seconds
        // @formatter:om
    }

    @Test
    public void testFile() throws Exception {
        final File path = new File(DownloadListings.DOWNLOAD_DIR);
        final FilenameFilter filter = (dir, name) -> name.endsWith("_mlsd.txt");
        final File[] files = path.listFiles(filter);
        if (files != null) {
            for (final File mlsd : files) {
                // System.out.println(mlsd);
                FTPListParseEngine engine = new FTPListParseEngine(MLSxEntryParser.getInstance());
                try (InputStream is = new FileInputStream(mlsd)) {
                    engine.readServerList(is, FTP.DEFAULT_CONTROL_ENCODING);
                }
                final FTPFile[] mlsds = engine.getFiles(FTPFileFilters.ALL);
                final File listFile = new File(mlsd.getParentFile(), mlsd.getName().replace("_mlsd", "_list"));
                try (InputStream inputStream = new FileInputStream(listFile)) {
                    final FTPClientConfig cfg = new FTPClientConfig();
                    cfg.setServerTimeZoneId("GMT");
                    final UnixFTPEntryParser parser = new UnixFTPEntryParser(cfg);
                    engine = new FTPListParseEngine(parser);
                    engine.readServerList(inputStream, FTP.DEFAULT_CONTROL_ENCODING);
                    compareSortedLists(mlsds, engine.getFiles(FTPFileFilters.ALL));
                }
            }
        }
    }

//    private boolean areSame(int a, int b, int d) {
//        return a == d || b == d || a == b;
//    }
//
//    private boolean areSame(String a, String b) {
//        return a.length() == 0 || b.length() == 0 || a.equals(b);
//    }
}