Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions distributions/demo-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ dependencies {
compile project(':extensions:auth:portability-auth-twitter')
compile project(':extensions:auth:portability-auth-imgur')
compile project(':extensions:auth:portability-auth-koofr')
compile project(':extensions:auth:portability-auth-synology')

compile project(':extensions:data-transfer:portability-data-transfer-apple')
compile project(':extensions:data-transfer:portability-data-transfer-daybook')
Expand All @@ -94,6 +95,7 @@ dependencies {
compile project(':extensions:data-transfer:portability-data-transfer-rememberthemilk')
compile project(':extensions:data-transfer:portability-data-transfer-smugmug')
compile project(':extensions:data-transfer:portability-data-transfer-spotify')
compile project(':extensions:data-transfer:portability-data-transfer-synology')
compile project(':extensions:data-transfer:portability-data-transfer-twitter')
compile project(':extensions:data-transfer:portability-data-transfer-imgur')
compile project(':extensions:data-transfer:portability-data-transfer-koofr')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.datatransferproject.types.common.models.DataVertical.CALENDAR;
import static org.datatransferproject.types.common.models.DataVertical.CONTACTS;
import static org.datatransferproject.types.common.models.DataVertical.MAIL;
import static org.datatransferproject.types.common.models.DataVertical.MEDIA;
import static org.datatransferproject.types.common.models.DataVertical.MUSIC;
import static org.datatransferproject.types.common.models.DataVertical.PHOTOS;
import static org.datatransferproject.types.common.models.DataVertical.SOCIAL_POSTS;
Expand Down Expand Up @@ -70,6 +71,7 @@ public Map<DataVertical, Set<String>> getExportScopes() {
.put(SOCIAL_POSTS, ImmutableSet.of("https://www.googleapis.com/auth/plus.login"))
.put(TASKS, ImmutableSet.of("https://www.googleapis.com/auth/tasks.readonly"))
.put(VIDEOS, ImmutableSet.of("https://www.googleapis.com/auth/photoslibrary.readonly"))
.put(MEDIA, ImmutableSet.of("https://www.googleapis.com/auth/photoslibrary.readonly"))
.put(MUSIC, ImmutableSet.of("https://www.googleapis.com/auth/music"))
.build();
}
Expand All @@ -85,6 +87,7 @@ public Map<DataVertical, Set<String>> getImportScopes() {
.put(PHOTOS, ImmutableSet.of("https://www.googleapis.com/auth/photoslibrary.appendonly"))
.put(TASKS, ImmutableSet.of("https://www.googleapis.com/auth/tasks"))
.put(VIDEOS, ImmutableSet.of("https://www.googleapis.com/auth/photoslibrary"))
.put(MEDIA, ImmutableSet.of("https://www.googleapis.com/auth/photoslibrary"))
.put(MUSIC, ImmutableSet.of("https://www.googleapis.com/auth/music"))
.build();
}
Expand Down
13 changes: 13 additions & 0 deletions extensions/auth/portability-auth-synology/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id 'maven'
id 'signing'
}

dependencies {
compile project(':portability-spi-api')
compile project(':portability-spi-cloud')
compile project(':libraries:auth')
}

configurePublication(project)

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.datatransferproject.auth.synology;

import org.datatransferproject.auth.OAuth2ServiceExtension;

public class SynologyAuthServiceExtension extends OAuth2ServiceExtension {
public SynologyAuthServiceExtension() {
super(new SynologyOAuthConfig());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.datatransferproject.auth.synology;

import static org.datatransferproject.types.common.models.DataVertical.MEDIA;
import static org.datatransferproject.types.common.models.DataVertical.PHOTOS;
import static org.datatransferproject.types.common.models.DataVertical.VIDEOS;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Map;
import java.util.Set;
import org.datatransferproject.auth.OAuth2Config;
import org.datatransferproject.types.common.models.DataVertical;

public class SynologyOAuthConfig implements OAuth2Config {
private static String AUTH_HOST = "https://identity.synology.com";
private static String USER_INFO_SCOPE = "userinfo";
private static String OFFLINE_ACCESS_SCOPE = "offline_access";

@Override
public String getServiceName() {
return "Synology";
}

@Override
public String getAuthUrl() {
return AUTH_HOST + "/oauth2/auth";
}

@Override
public String getTokenUrl() {
return AUTH_HOST + "/oauth2/token";
}

@Override
public Map<DataVertical, Set<String>> getExportScopes() {
return ImmutableMap.<DataVertical, Set<String>>builder()
.put(PHOTOS, ImmutableSet.of(USER_INFO_SCOPE, OFFLINE_ACCESS_SCOPE))
.put(VIDEOS, ImmutableSet.of(USER_INFO_SCOPE, OFFLINE_ACCESS_SCOPE))
.put(MEDIA, ImmutableSet.of(USER_INFO_SCOPE, OFFLINE_ACCESS_SCOPE))
.build();
}

@Override
public Map<DataVertical, Set<String>> getImportScopes() {
return ImmutableMap.<DataVertical, Set<String>>builder()
.put(PHOTOS, ImmutableSet.of(USER_INFO_SCOPE, OFFLINE_ACCESS_SCOPE))
.put(VIDEOS, ImmutableSet.of(USER_INFO_SCOPE, OFFLINE_ACCESS_SCOPE))
.put(MEDIA, ImmutableSet.of(USER_INFO_SCOPE, OFFLINE_ACCESS_SCOPE))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package org.datatransferproject.auth.synology;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.datatransferproject.auth.synology.SynologyAuthServiceExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Synology
This folder contains the extension implementation for the
[Synology](https://www.synology.com).

## Data Supported

Synology currently supports the following data types:
- Photos
- Videos

## Current State

- Photos/Videos:
- Exporting is supported via Synology Photos or Bee Photos.
- Importing is supported via the exporting source platforms (currently only Google Photos).

## Keys & Auth
Synology uses OAuth 2 for authorization.

### Create an OAuth App
Contact [Synology DTP Developer Team](dtp@synology.com) for further information.


Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2025 The Data Transfer Project Authors.
*
* 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
*
* 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.
*
*/

plugins {
id 'maven'
id 'signing'
}

dependencies {
compile project(':portability-spi-transfer')
compile project(':portability-spi-cloud')
compile project(':portability-transfer')
compile "com.squareup.okhttp3:okhttp:${okHttpVersion}"
}

configurePublication(project)
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.datatransferproject.datatransfer.synology;

import static org.datatransferproject.types.common.models.DataVertical.MEDIA;
import static org.datatransferproject.types.common.models.DataVertical.PHOTOS;
import static org.datatransferproject.types.common.models.DataVertical.VIDEOS;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import okhttp3.OkHttpClient;
import org.datatransferproject.api.launcher.ExtensionContext;
import org.datatransferproject.api.launcher.Monitor;
import org.datatransferproject.datatransfer.synology.media.SynologyMediaImporter;
import org.datatransferproject.datatransfer.synology.photos.SynologyPhotosImporter;
import org.datatransferproject.datatransfer.synology.service.SynologyDTPService;
import org.datatransferproject.datatransfer.synology.service.SynologyOAuthTokenManager;
import org.datatransferproject.datatransfer.synology.uploader.SynologyUploader;
import org.datatransferproject.datatransfer.synology.videos.SynologyVideosImporter;
import org.datatransferproject.spi.cloud.storage.AppCredentialStore;
import org.datatransferproject.spi.cloud.storage.JobStore;
import org.datatransferproject.spi.transfer.extension.TransferExtension;
import org.datatransferproject.spi.transfer.idempotentexecutor.IdempotentImportExecutor;
import org.datatransferproject.spi.transfer.idempotentexecutor.IdempotentImportExecutorExtension;
import org.datatransferproject.spi.transfer.provider.Exporter;
import org.datatransferproject.spi.transfer.provider.Importer;
import org.datatransferproject.transfer.JobMetadata;
import org.datatransferproject.types.common.models.DataVertical;
import org.datatransferproject.types.transfer.auth.AppCredentials;
import org.datatransferproject.types.transfer.serviceconfig.TransferServiceConfig;

public class SynologyTransferExtension implements TransferExtension {
private static final ImmutableList<DataVertical> SUPPORTED_SERVICES =
ImmutableList.of(PHOTOS, VIDEOS, MEDIA);
private Monitor monitor;

private boolean initialized = false;

private ImmutableMap<DataVertical, Importer> importerMap;

@Override
public String getServiceId() {
return "Synology";
}

private String getExtensionKey() {
return getServiceId().toUpperCase() + "_KEY";
}

private String getExtensionSecret() {
return getServiceId().toUpperCase() + "_SECRET";
}

@Override
public Exporter<?, ?> getExporter(DataVertical transferDataType) {
return null;
}

@Override
public Importer<?, ?> getImporter(DataVertical transferDataType) {
Preconditions.checkArgument(initialized);
Preconditions.checkArgument(SUPPORTED_SERVICES.contains(transferDataType));
monitor.info(() -> "Getting importer for " + transferDataType);
monitor.info(() -> "has importer: " + importerMap.containsKey(transferDataType));
return importerMap.get(transferDataType);
}

@Override
public void initialize(ExtensionContext context) {
if (initialized) {
return;
}

monitor = context.getMonitor();

AppCredentials appCredentials;
try {
appCredentials =
context
.getService(AppCredentialStore.class)
.getAppCredentials(getExtensionKey(), getExtensionSecret());

} catch (Exception e) {
monitor.info(
() ->
"Unable to retrieve Synology AppCredentials. Please configure Synology KEY and"
+ " SECRET.");
return;
}

JobStore jobStore = context.getService(JobStore.class);
IdempotentImportExecutor idempotentImportExecutor =
context
.getService(IdempotentImportExecutorExtension.class)
.getRetryingIdempotentImportExecutor(context);
SynologyOAuthTokenManager tokenManager = new SynologyOAuthTokenManager(appCredentials, monitor);
OkHttpClient client = context.getService(OkHttpClient.class);
TransferServiceConfig transferServiceConfig = context.getService(TransferServiceConfig.class);
SynologyDTPService synologyDTPService =
new SynologyDTPService(
monitor,
transferServiceConfig,
JobMetadata.getExportService(),
jobStore,
tokenManager,
client);
SynologyUploader synologyUploader =
new SynologyUploader(idempotentImportExecutor, monitor, synologyDTPService);

ImmutableMap.Builder<DataVertical, Importer> importerBuilder = ImmutableMap.builder();
importerBuilder.put(MEDIA, new SynologyMediaImporter(monitor, tokenManager, synologyUploader));
importerBuilder.put(
PHOTOS, new SynologyPhotosImporter(monitor, tokenManager, synologyUploader));
importerBuilder.put(
VIDEOS, new SynologyVideosImporter(monitor, tokenManager, synologyUploader));
importerMap = importerBuilder.build();

monitor.info(() -> "Initializing SynologyTransferExtension");
initialized = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2025 The Data Transfer Project Authors.
*
* 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
*
* 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.datatransferproject.datatransfer.synology.constant;

/** Defines constants for Synology data transfer. */
public interface SynologyConstant {
public static final String ALBUM_ITEM_ID_FORMAT = "%s-%s";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2025 The Data Transfer Project Authors.
*
* 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
*
* 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.datatransferproject.datatransfer.synology.constant;

/**
* Defines error codes for Synology data transfer.
*
* <p>The codes are structured as follows:
*
* <ul>
* <li>S: Service identifier
* <li>MM: Module identifier
* <li>SS: Specific error code
* </ul>
*
* <p>Example: 11001 represents a DTP Client error with the code MAX_RETRIES_EXCEEDED where 1 is the
* service identifier, 10 is the module identifier, and 01 is the specific error code.
*/
public interface SynologyErrorCode {
// [S][MM][SS]
// S: Service identifier
// MM: Module identifier
// SS: Specific error code

// Common Errors

// DTP Client Errors
public static final int MAX_RETRIES_EXCEEDED = 11001;
public static final int IMPORT_FAILED = 11002;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2025 The Data Transfer Project Authors.
*
* 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
*
* 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.datatransferproject.datatransfer.synology.exceptions;

/**
* Base class for exceptions thrown by the Synology DTP client. Business logic exceptions should
* extend this class.
*/
public abstract class SynologyDTPClientException extends SynologyException {
protected SynologyDTPClientException(String message, String errorCode) {
super(message, errorCode);
}

protected SynologyDTPClientException(String message, Throwable cause, String errorCode) {
super(message, cause, errorCode);
}
}
Loading