/*
* Copyright 2013-2017 consulo.io
*
* Licensed 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
*
* http://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 consulo.externalStorage.storage;
import com.google.gson.Gson;
import com.intellij.errorreport.error.AuthorizationFailedException;
import com.intellij.openapi.components.RoamingType;
import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.impl.stores.StateStorageManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.concurrency.AppExecutorUtil;
import consulo.externalStorage.ExternalStorageUtil;
import consulo.ide.webService.WebServiceApi;
import consulo.ide.webService.WebServicesConfiguration;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.*;
/**
* @author VISTALL
* @since 12-Feb-17
*/
class ExternalStorageQueue {
static class PushFile {
private int modCount;
}
static class LoadItem {
private final String myFileSpec;
private final RoamingType myRoamingType;
private final int myModCount;
private final StateStorageManager myStateStorageManager;
LoadItem(String fileSpec, RoamingType roamingType, int modCount, StateStorageManager stateStorageManager) {
myFileSpec = fileSpec;
myRoamingType = roamingType;
myModCount = modCount;
myStateStorageManager = stateStorageManager;
}
}
private static final byte[] ourNotModifiedBytes = new byte[0];
private final List<LoadItem> myLoadItems = new CopyOnWriteArrayList<>();
private final Map<String, Ref<byte[]>> myLoadedBytes = new ConcurrentHashMap<>();
private Future<?> myLoadTask = CompletableFuture.completedFuture(null);
@Nullable
public Ref<byte[]> getContent(String fileSpec, RoamingType roamingType) {
return myLoadedBytes.remove(ExternalStorage.buildFileSpec(roamingType, fileSpec));
}
void wantLoad(@NotNull String fileSpec, @NotNull RoamingType roamingType, int mod, @NotNull StateStorageManager stateStorageManager) {
myLoadTask.cancel(false);
myLoadItems.add(new LoadItem(fileSpec, roamingType, mod, stateStorageManager));
myLoadTask = AppExecutorUtil.getAppScheduledExecutorService().schedule(this::executeLoad, 1, TimeUnit.MINUTES);
}
void executeLoad() {
if (myLoadItems.isEmpty()) {
return;
}
LoadItem[] items = myLoadItems.toArray(new LoadItem[myLoadItems.size()]);
myLoadItems.removeAll(Arrays.asList(items));
Map<LoadItem, byte[]> map = new HashMap<>();
Arrays.stream(items)/*.parallel()*/.forEach(item -> {
URIBuilder urlBuilder;
try {
urlBuilder = new URIBuilder(WebServiceApi.SYNCHRONIZE_API.buildUrl("getFile"));
}
catch (URISyntaxException e1) {
throw new RuntimeException(e1);
}
urlBuilder.addParameter("filePath", ExternalStorage.buildFileSpec(item.myRoamingType, item.myFileSpec));
urlBuilder.addParameter("modCount", String.valueOf(item.myModCount));
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpGet request = new HttpGet(urlBuilder.build());
String authKey = WebServicesConfiguration.getInstance().getOAuthKey(WebServiceApi.SYNCHRONIZE_API);
if (authKey != null) {
request.addHeader("Authorization", authKey);
}
byte[] data = client.execute(request, response -> {
int statusCode = response.getStatusLine().getStatusCode();
switch (statusCode) {
case HttpURLConnection.HTTP_UNAUTHORIZED:
throw new AuthorizationFailedException();
case HttpURLConnection.HTTP_NOT_FOUND:
return null;
case HttpURLConnection.HTTP_NOT_MODIFIED:
return ourNotModifiedBytes;
case HttpURLConnection.HTTP_OK:
return EntityUtils.toByteArray(response.getEntity());
default:
throw new RuntimeException("not OK: " + statusCode);
}
});
// parallel stream
synchronized (map) {
map.put(item, data);
}
}
catch (AuthorizationFailedException ae) {
ae.printStackTrace();
}
catch (Exception e) {
e.printStackTrace();
}
});
Set<StateStorage> stateStorages = new HashSet<>();
for (Map.Entry<LoadItem, byte[]> entry : map.entrySet()) {
try {
LoadItem key = entry.getKey();
byte[] value = entry.getValue();
// not changed
if (value == ourNotModifiedBytes) {
continue;
}
StateStorageManager stateStorageManager = entry.getKey().myStateStorageManager;
StateStorage storage = stateStorageManager.getStateStorage(key.myFileSpec, key.myRoamingType);
assert storage != null;
myLoadedBytes.put(ExternalStorage.buildFileSpec(key.myRoamingType, key.myFileSpec), Ref.create(value));
stateStorages.add(storage);
System.out.println("changed" + key.myFileSpec);
}
catch (Exception e) {
e.printStackTrace();
}
}
/* ComponentStoreImpl.ReloadComponentStoreStatus status =
ComponentStoreImpl.reloadStore(stateStorages, ((ApplicationEx2)ApplicationManager.getApplication()).getStateStore());
if (status == ComponentStoreImpl.ReloadComponentStoreStatus.RESTART_AGREED) {
ApplicationManagerEx.getApplicationEx().restart(true);
} */
}
void wantSave(@NotNull File proxyDirectory, @NotNull String fileSpec, RoamingType roamingType, byte[] content) throws IOException {
File file = new File(proxyDirectory, ExternalStorage.buildFileSpec(roamingType, fileSpec));
FileUtil.createParentDirs(file);
FileUtil.writeToFile(file, content);
try (CloseableHttpClient client = HttpClients.createDefault()) {
URIBuilder urlBuilder = new URIBuilder(WebServiceApi.SYNCHRONIZE_API.buildUrl("pushFile"));
urlBuilder.addParameter("filePath", ExternalStorage.buildFileSpec(roamingType, fileSpec));
HttpPost request = new HttpPost(urlBuilder.build());
request.setEntity(new ByteArrayEntity(content));
String authKey = WebServicesConfiguration.getInstance().getOAuthKey(WebServiceApi.SYNCHRONIZE_API);
if (authKey != null) {
request.addHeader("Authorization", authKey);
}
PushFile pushFile = client.execute(request, response -> {
if (response.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
throw new AuthorizationFailedException();
}
return new Gson().fromJson(EntityUtils.toString(response.getEntity()), PushFile.class);
});
File modCountFile = ExternalStorageUtil.getModCountFile(file);
FileUtil.writeToFile(modCountFile, String.valueOf(pushFile.modCount));
}
catch (URISyntaxException e) {
throw new IOException(e);
}
}
}