summaryrefslogtreecommitdiff
path: root/src/main/java/com/it_jaros/jscanner/ProgressBar.java
blob: 12a359c0aa3351ae630d8570bb6d11e46e6b320c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package com.it_jaros.jscanner;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ProgressBar {

    private final List<Progress> hosts = new ArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();
    private final ScheduledExecutorService ui = Executors.newSingleThreadScheduledExecutor();
    private final boolean quiet;
    private final int columnWidth = ProgressBar.getColumnWidth();

    public ProgressBar(boolean quiet) {
        this.quiet = quiet;
    }

    private static int getColumnWidth() {
        try {
            Process process = new ProcessBuilder("sh", "-c", "stty size < /dev/tty").start();

            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line = reader.readLine();
                if (line != null && !line.isBlank()) {
                    Matcher matcher = Pattern.compile("^\\d+\\s+(\\d+)").matcher(line.trim());
                    if (matcher.matches()) {
                        return Integer.parseInt(matcher.group(1));
                    }
                }
            }
        } catch (Exception ignored) {
            // does not matter why it did not work
        }

        return 120;
    }

    public void start() {
        if (!quiet) {
            System.out.print("Scanning targets...\n\n");
        }

        // although we don't print anything, we still need to
        // gc all the done hosts
        ui.scheduleAtFixedRate(() -> {
            lock.lock();
            drawProgress(false);
            lock.unlock();
        }, 1, 500, TimeUnit.MILLISECONDS);
    }

    private void drawProgress(boolean last) {
        // sort -> the more progressed the host the more up it is
        hosts.sort(Comparator.comparingInt((Progress p) -> p.done().get()).reversed());

        // find and print finished hosts one last time
        for (Iterator<Progress> it = hosts.iterator(); it.hasNext(); ) {
            Progress p = it.next();
            if (p.done().get() == p.total()) {
                it.remove();
                drawBar(p);
            } else {
                break;
            }
        }

        for (Progress progress : hosts) {
            drawBar(progress);
        }

        if (!last) {
            // move cursor up again
            if (!quiet) {
                System.out.print("\u001b[" + hosts.size() + "A");
            }
        }
    }

    public void add(Progress progress) {
        lock.lock();
        hosts.add(progress);
        lock.unlock();
    }

    private void drawBar(Progress progress) {
        if (quiet) return;
        System.out.print("\u001b[2K\r"); // clear whole line
        System.out.println(createBar(
                progress.host(),
                progress.open().get(),
                progress.filtered().get(),
                progress.done().get(),
                progress.total()));
    }

    public String createBar(String label, int open, int filtered, int done, int total) {
        int terminalWidth = columnWidth;
        int bracketWidth = terminalWidth / 3;
        double pct = total == 0 ? 1.0 : (done / (double) total);
        int percent = (int) (pct * 100);

        String statsLeft = String.format("open:%d filtered:%d (%d/%d)", open, filtered, done, total);
        String statsRight = String.format("%3d%%", percent);
        int filled = (int) (pct * bracketWidth);
        String brackets = "[" + "#".repeat(filled) + "-".repeat(bracketWidth - filled) + "]";

        int spacesBetween = terminalWidth - bracketWidth - (label + statsLeft + statsRight).length() - 5;
        return String.format("%s%s%s %s %s", label, " ".repeat(spacesBetween), statsLeft, brackets, statsRight);
    }

    public void stop() {
        ui.shutdown();
        try {
            ui.awaitTermination(1, TimeUnit.SECONDS);
            // we need to draw one last time for the 100% to appear
            drawProgress(true);
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
    }
}