aboutsummaryrefslogtreecommitdiffstats
path: root/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/Downloads.java
blob: f3c3a91f7871ac297393354f956ccca9321a609a (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
133
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.filedistribution;

import com.yahoo.config.FileReference;

import java.io.File;
import java.time.Instant;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Keeps track of downloads and download status
 *
 * @author hmusum
 */
public class Downloads {

    private static final Logger log = Logger.getLogger(Downloads.class.getName());

    private final Map<FileReference, FileReferenceDownload> downloads = new ConcurrentHashMap<>();
    private final DownloadStatuses downloadStatuses = new DownloadStatuses();

    public DownloadStatuses downloadStatuses() { return downloadStatuses; }

    void setDownloadStatus(FileReference fileReference, double completeness) {
        downloadStatuses.put(fileReference, completeness);
    }

    void completedDownloading(FileReference fileReference, File file) {
        Optional<FileReferenceDownload> download = get(fileReference);
        setDownloadStatus(fileReference, 1.0);
        if (download.isPresent()) {
            downloads.remove(fileReference);
            download.get().future().complete(Optional.of(file));
        } else {
            log.log(Level.FINE, () -> "Received '" + fileReference + "', which was not requested. Can be ignored if happening during upgrades/restarts");
        }
    }

    void add(FileReferenceDownload fileReferenceDownload) {
        downloads.put(fileReferenceDownload.fileReference(), fileReferenceDownload);
        downloadStatuses.put(fileReferenceDownload.fileReference());
    }

    void remove(FileReference fileReference) {
        downloadStatuses.get(fileReference).ifPresent(d -> new DownloadStatus(d.fileReference(), 0.0));
        downloads.remove(fileReference);
    }

    double downloadStatus(FileReference fileReference) {
        double status = 0.0;
        Optional<Downloads.DownloadStatus> downloadStatus = downloadStatuses.get(fileReference);
        if (downloadStatus.isPresent()) {
            status = downloadStatus.get().progress();
        }
        return status;
    }

    Map<FileReference, Double> downloadStatus() {
        return downloadStatuses.all().values().stream().collect(Collectors.toMap(Downloads.DownloadStatus::fileReference, Downloads.DownloadStatus::progress));
    }

    Optional<FileReferenceDownload> get(FileReference fileReference) {
        return Optional.ofNullable(downloads.get(fileReference));
    }

    /* Status for ongoing and completed downloads, keeps at most status for 100 last downloads */
    static class DownloadStatuses {

        private static final int maxEntries = 100;

        private final Map<FileReference, DownloadStatus> downloadStatus = Collections.synchronizedMap(new HashMap<>());

        void put(FileReference fileReference) {
            put(fileReference, 0.0);
        }

        void put(FileReference fileReference, double progress) {
            downloadStatus.put(fileReference, new DownloadStatus(fileReference, progress));
            if (downloadStatus.size() > maxEntries) {
                Map.Entry<FileReference, DownloadStatus> oldest =
                        Collections.min(downloadStatus.entrySet(), Comparator.comparing(e -> e.getValue().created));
                downloadStatus.remove(oldest.getKey());
            }
        }

        Optional<DownloadStatus> get(FileReference fileReference) {
            return Optional.ofNullable(downloadStatus.get(fileReference));
        }

        Map<FileReference, DownloadStatus> all() {
            return Map.copyOf(downloadStatus);
        }

        @Override
        public String toString() {
            return downloadStatus.entrySet().stream().map(entry -> entry.getKey().value() + "=>" + entry.getValue().progress).collect(Collectors.joining(", "));
        }

    }

    static class DownloadStatus {
        private final FileReference fileReference;
        private final double progress; // between 0 and 1
        private final Instant created;

        DownloadStatus(FileReference fileReference, double progress) {
            this.fileReference = fileReference;
            this.progress = progress;
            this.created = Instant.now();
        }

        public FileReference fileReference() {
            return fileReference;
        }

        public double progress() {
            return progress;
        }

        public Instant created() {
            return created;
        }
    }

}