1 /*
  2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.skara.storage;
 24 
 25 import org.openjdk.skara.forge.HostedRepository;
 26 import org.openjdk.skara.vcs.*;
 27 
 28 import java.io.*;
 29 import java.nio.file.*;
 30 import java.util.*;
 31 
 32 class HostedRepositoryStorage<T> implements Storage<T> {
 33     private final HostedRepository hostedRepository;
 34     private final String ref;
 35     private final String fileName;
 36     private final String authorName;
 37     private final String authorEmail;
 38     private final String message;
 39     private final Repository localRepository;
 40     private final StorageSerializer<T> serializer;
 41     private final StorageDeserializer<T> deserializer;
 42 
 43     private Hash hash;
 44     private RepositoryStorage<T> repositoryStorage;
 45     private Set<T> current;
 46 
 47     HostedRepositoryStorage(HostedRepository repository, Path localStorage, String ref, String fileName, String authorName, String authorEmail, String message, StorageSerializer<T> serializer, StorageDeserializer<T> deserializer) {
 48         this.hostedRepository = repository;
 49         this.ref = ref;
 50         this.fileName = fileName;
 51         this.authorEmail = authorEmail;
 52         this.authorName = authorName;
 53         this.message = message;
 54         this.serializer = serializer;
 55         this.deserializer = deserializer;
 56 
 57         try {
 58             Repository localRepository;
 59             try {
 60                 localRepository = Repository.materialize(localStorage, repository.url(), "+" + ref + ":storage");
 61             } catch (IOException e) {
 62                 // The remote ref may not yet exist
 63                 localRepository = Repository.init(localStorage, repository.repositoryType());
 64                 var storage = Files.writeString(localStorage.resolve(fileName), "");
 65                 localRepository.add(storage);
 66                 localRepository.commit(message, authorName, authorEmail);
 67             }
 68             this.localRepository = localRepository;
 69             hash = localRepository.head();
 70             repositoryStorage = new RepositoryStorage<>(localRepository, fileName, authorName, authorEmail, message, serializer, deserializer);
 71             current = current();
 72         } catch (IOException e) {
 73             throw new UncheckedIOException(e);
 74         }
 75     }
 76 
 77     @Override
 78     public Set<T> current() {
 79         return repositoryStorage.current();
 80     }
 81 
 82     @Override
 83     public void put(Collection<T> items) {
 84         int retryCount = 0;
 85         IOException lastException = null;
 86         Hash lastRemoteHash = null;
 87 
 88         while (retryCount < 10) {
 89             // Update our local storage
 90             repositoryStorage.put(items);
 91             var updated = repositoryStorage.current();
 92             if (current.equals(updated)) {
 93                 return;
 94             }
 95 
 96             // The local storage has changed, try to push it to the remote
 97             try {
 98                 var updatedHash = localRepository.head();
 99                 localRepository.push(updatedHash, hostedRepository.url(), ref);
100                 hash = updatedHash;
101                 current = updated;
102                 return;
103             } catch (IOException e) {
104                 lastException = e;
105 
106                 // Check if the remote has changed
107                 try {
108                     var remoteHash = localRepository.fetch(hostedRepository.url(), ref);
109                     if (!remoteHash.equals(lastRemoteHash)) {
110                         localRepository.checkout(remoteHash, true);
111                         repositoryStorage = new RepositoryStorage<>(localRepository, fileName, authorName, authorEmail, message, serializer, deserializer);
112                         lastRemoteHash = remoteHash;
113 
114                         // We are making progress catching up with remote changes, don't update the retryCount
115                         continue;
116                     }
117                 } catch (IOException e1) {
118                     lastException = e1;
119                 }
120                 retryCount++;
121             }
122         }
123 
124         throw new UncheckedIOException("Retry count exceeded", lastException);
125     }
126 }