diff --git a/.gitignore b/.gitignore index 0d7b5f69dcf7..61ba6e10778e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,7 @@ META-INF/ .DS_Store ##Omito los datos relativos a la configuración local de dspace -install/ \ No newline at end of file +install/ + +# Omito las credenciales de google api +client_secrets.json diff --git a/dspace/config/cron.d-dspace b/dspace/config/cron.d-dspace index c1e494eb0302..d7e9251340b2 100644 --- a/dspace/config/cron.d-dspace +++ b/dspace/config/cron.d-dspace @@ -80,3 +80,9 @@ DSPACE=/var/dspace/install #Limpieza de la cache de cocoon cada 8 horas a partir de las 7am 30 */8 * * * dspace curl --silent --output /dev/null --show-error --fail --data "cache-clearance" http://sedici.unlp.edu.ar/clear-cache + +#Cron para ejecutar tareas relacionadas con youtube, se ejecuta de lunes a viernes, de 8 a 18 cada una hora +#0 8-18 * * 1-5 dspace $DSPACE/bin/dspace curate -q replicateVideo > /dev/null + +#Cron identica a de arriba pero para probar en testing +0 8-18 * * 1-5 root docker exec sedici sh -c "cd /dspace/install/bin && ./dspace curate -q replicateVideo"> /dev/null \ No newline at end of file diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index af6a30a64c05..cd7b379be1b9 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -768,7 +768,7 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add doi here if you are using org.dspace.identifier.DOIIdentifierProvider to generate DOIs. # Adding doi here makes DSpace send metadata updates to your doi registration agency. # Add rdf here, if you are using dspace-rdf to export your repository content as RDF. -event.dispatcher.default.consumers = discovery, eperson, harvester, doi +event.dispatcher.default.consumers = discovery, eperson, harvester, doi, videouploader # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher @@ -814,6 +814,10 @@ event.consumer.versioning.filters = Item+Install event.consumer.authority.class = org.dspace.authority.indexer.AuthorityConsumer event.consumer.authority.filters = Item+Modify|Modify_Metadata +# consumer to upload videos +event.consumer.videouploader.class = ar.edu.unlp.sedici.dspace.uploader.VideoUploaderEventConsumer +event.consumer.videouploader.filters = Collection|Item|Bundle|Bitstream+Add|Modify_Metadata|Remove + # ...set to true to enable testConsumer messages to standard output #testConsumer.verbose = true @@ -1966,6 +1970,10 @@ webui.bitstream.order.field = bitstream_order google-metadata.config = ${dspace.dir}/config/crosswalks/google-metadata.properties google-metadata.enable = true +##### Youtube Uploader Configuration ##### +youtube.upload.redirect_uri=${youtube.upload.redirect_uri} +youtube.enable = true + #---------------------------------------------------------------# #--------------JSPUI SPECIFIC CONFIGURATIONS--------------------# #---------------------------------------------------------------# diff --git a/dspace/config/input-forms.xml b/dspace/config/input-forms.xml index e083896c1183..7f5e9ff7ecc9 100644 --- a/dspace/config/input-forms.xml +++ b/dspace/config/input-forms.xml @@ -1009,6 +1009,19 @@ Objeto de aprendizaje + + + sedici + description + catedra + true + + textarea + Nombre de la catedra + Bibliotecarios + Objeto de aprendizaje + + @@ -1243,6 +1256,10 @@ Objeto de aprendizaje Objeto de aprendizaje + + Video de catedras + Video de catedras + diff --git a/dspace/config/log4j.properties b/dspace/config/log4j.properties index 45c4130bdcdd..2233f92d2f7a 100644 --- a/dspace/config/log4j.properties +++ b/dspace/config/log4j.properties @@ -89,6 +89,19 @@ log4j.appender.A3.File=${log.dir}/cocoon.log log4j.appender.A3.layout=org.apache.log4j.PatternLayout log4j.appender.A3.layout.ConversionPattern=%d %-5p %c %x - %m%n +########################################################################### +# Appender for the uploads +########################################################################### +log4j.appender.upload=org.apache.log4j.FileAppender +log4j.appender.upload.File=${log.dir}/upload.log +log4j.appender.upload.layout=org.apache.log4j.PatternLayout +log4j.appender.upload.layout.ConversionPattern=%d %-5p %c @ %m%n + +log4j.logger.ar.edu.unlp.sedici.dspace.uploader=INFO, upload +log4j.logger.ar.edu.unlp.sedici.dspace.curation.VideoDeleteTask=INFO, upload +log4j.logger.ar.edu.unlp.sedici.dspace.curation.VideoUpdaterTask=INFO, upload +log4j.logger.ar.edu.unlp.sedici.dspace.curation.VideoUploaderTask=INFO, upload + ########################################################################### # Other settings diff --git a/dspace/config/modules/curate.cfg b/dspace/config/modules/curate.cfg index 23a6cf57575e..d41e80d95185 100644 --- a/dspace/config/modules/curate.cfg +++ b/dspace/config/modules/curate.cfg @@ -12,6 +12,9 @@ plugin.named.org.dspace.curate.CurationTask = \ ar.edu.unlp.sedici.dspace.curation.MetadataAuthorityQualityControl = metadataauthorityqualitycontrol-fixClosedAuthorities, \ ar.edu.unlp.sedici.dspace.curation.MetadataAuthorityQualityControl = metadataauthorityqualitycontrol-fixInvalidAuthorities, \ ar.edu.unlp.sedici.dspace.curation.PDFVersionChecker = PDFversionchecker, \ + ar.edu.unlp.sedici.dspace.curation.VideoUploaderTask = VideoUploaderTask, \ + ar.edu.unlp.sedici.dspace.curation.VideoUpdaterTask = VideoUpdaterTask, \ + ar.edu.unlp.sedici.dspace.curation.VideoDeleteTask = VideoDeleteTask, \ org.dspace.ctask.general.ProfileFormats = profileformats, \ org.dspace.ctask.general.RequiredMetadata = requiredmetadata, \ org.dspace.ctask.general.ClamScan = vscan, \ diff --git a/dspace/config/modules/upload.cfg b/dspace/config/modules/upload.cfg new file mode 100644 index 000000000000..a24c9cf84afa --- /dev/null +++ b/dspace/config/modules/upload.cfg @@ -0,0 +1,18 @@ +# Uri de redireccionamiento de las credenciales +#youtube.upload.redirect_uri= + +# Usuario reencola las tareas +youtube.upload.user=usuarioEncolador@test.com + +# Ruta de credenciales +youtube.upload.secrets=${dspace.dir}/config/client_secrets.json + +# Ruta de refresh token +youtube.upload.refresh=${dspace.dir}/config/youtube-api-uploadvideo.json + +# Privacidad del video public/unlisted/private +youtube.upload.video.state=private + +# Metadato que identifica al bitstream en otra plataforma externa +video.identifier.metadata=sedici.identifier.youtubeId + diff --git a/dspace/config/registries/sedici-metadata.xml b/dspace/config/registries/sedici-metadata.xml index 39a8dd594ede..a3f15835fa0f 100644 --- a/dspace/config/registries/sedici-metadata.xml +++ b/dspace/config/registries/sedici-metadata.xml @@ -316,6 +316,18 @@ expediente Numero de expediente asociado a un documento institucional + + sedici + identifier + youtubeId + Id de video replicado en Youtube + + + sedici + description + catedra + Nombre de catedra a la que pertenece el recurso + sedici relation diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index d02b96e54b29..f326a526a0a3 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -24,6 +24,7 @@ + diff --git a/dspace/config/spring/api/upload.xml b/dspace/config/spring/api/upload.xml new file mode 100644 index 000000000000..0f11ad1d5e66 --- /dev/null +++ b/dspace/config/spring/api/upload.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 834a4a1c037f..42e1ab2c9eab 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -24,8 +24,17 @@ ${basedir}/../../.. + v3-rev20230123-2.0.0 + UTF-8 + + + google-api-services + http://google-api-client-libraries.appspot.com/mavenrepo + + + oracle-support @@ -97,6 +106,54 @@ + + + + + com.google.apis + google-api-services-youtube + ${project.youtube.version} + + + com.google.http-client + google-http-client-jackson2 + 1.42.3 + + + com.google.oauth-client + google-oauth-client-jetty + 1.34.1 + + + com.google.api-client + google-api-client + 2.2.0 + + + com.google.http-client + google-http-client + 1.42.3 + + + com.google.oauth-client + google-oauth-client + 1.34.1 + + + com.google.code.gson + gson + 2.10.1 + + + com.google.guava + guava + 31.1-jre + + + org.jsoup + jsoup + 1.15.4 + diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoDeleteTask.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoDeleteTask.java new file mode 100644 index 000000000000..72a83c3c4da4 --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoDeleteTask.java @@ -0,0 +1,45 @@ +package ar.edu.unlp.sedici.dspace.curation; + +import java.io.IOException; +import java.sql.SQLException; + +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Context; +import org.dspace.curate.AbstractCurationTask; +import org.dspace.curate.Curator; +import org.dspace.eperson.EPerson; +import org.dspace.utils.DSpace; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import ar.edu.unlp.sedici.dspace.uploader.VideoUploaderServiceImpl; + +public class VideoDeleteTask extends AbstractCurationTask { + + private int status; + + private static final Logger log = Logger.getLogger(VideoDeleteTask.class); + + @Override + public int perform(DSpaceObject dso) throws IOException { + + status = Curator.CURATE_SKIP; + Item item = (Item) dso; + + + VideoUploaderServiceImpl vuploader = new DSpace().getSingletonService(VideoUploaderServiceImpl.class); + try { + vuploader.removeContent(item); + status = Curator.CURATE_SUCCESS; + } catch (IOException e) { + log.error("IO error in the delete of the item with ID "+item.getID()+": "+e.getMessage(),e); + throw e; + } + + return status; + } + +} \ No newline at end of file diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUpdaterTask.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUpdaterTask.java new file mode 100644 index 000000000000..5e915f65f4ab --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUpdaterTask.java @@ -0,0 +1,45 @@ +package ar.edu.unlp.sedici.dspace.curation; + +import java.io.IOException; +import java.sql.SQLException; + +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Context; +import org.dspace.curate.AbstractCurationTask; +import org.dspace.curate.Curator; +import org.dspace.eperson.EPerson; +import org.dspace.utils.DSpace; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import ar.edu.unlp.sedici.dspace.uploader.VideoUploaderServiceImpl; + +public class VideoUpdaterTask extends AbstractCurationTask { + + private int status; + + private static final Logger log = Logger.getLogger(VideoUpdaterTask.class); + + @Override + public int perform(DSpaceObject dso) throws IOException { + + status = Curator.CURATE_SKIP; + Item item = (Item) dso; + + + VideoUploaderServiceImpl vuploader = new DSpace().getSingletonService(VideoUploaderServiceImpl.class); + try { + vuploader.modifyContent(item); + status = Curator.CURATE_SUCCESS; + } catch (IOException e) { + log.error("IO error in the update of the item with ID "+item.getID()+": "+e.getMessage(),e); + throw e; + } + + return status; + } + +} diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUploaderTask.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUploaderTask.java new file mode 100644 index 000000000000..f720e21c02d1 --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/curation/VideoUploaderTask.java @@ -0,0 +1,45 @@ +package ar.edu.unlp.sedici.dspace.curation; + +import java.io.IOException; +import java.sql.SQLException; + +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Context; +import org.dspace.curate.AbstractCurationTask; +import org.dspace.curate.Curator; +import org.dspace.eperson.EPerson; +import org.dspace.utils.DSpace; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import ar.edu.unlp.sedici.dspace.uploader.VideoUploaderServiceImpl; + +public class VideoUploaderTask extends AbstractCurationTask { + + private int status; + + private static final Logger log = Logger.getLogger(VideoUploaderTask.class); + + @Override + public int perform(DSpaceObject dso) throws IOException { + + status = Curator.CURATE_SKIP; + Item item = (Item) dso; + + + VideoUploaderServiceImpl vuploader = new DSpace().getSingletonService(VideoUploaderServiceImpl.class); + try { + vuploader.uploadContent(item); + status = Curator.CURATE_SUCCESS; + } catch (IOException e) { + log.error("IO error in the upload of the item with ID "+item.getID()+": "+e.getMessage(),e); + throw e; + } + + return status; + } + +} diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/ContentUploaderService.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/ContentUploaderService.java new file mode 100644 index 000000000000..71266ef96323 --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/ContentUploaderService.java @@ -0,0 +1,14 @@ +package ar.edu.unlp.sedici.dspace.uploader; + +import java.lang.Throwable; +import org.dspace.content.Item; + + +public interface ContentUploaderService { + + void uploadContent(Item item) throws Throwable; + void removeContent(Item item) throws Throwable; + void modifyContent(Item item) throws Throwable; + +} + diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/SolrServiceYoutubeBitstreamPlugin.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/SolrServiceYoutubeBitstreamPlugin.java new file mode 100644 index 000000000000..bdfedfd61628 --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/SolrServiceYoutubeBitstreamPlugin.java @@ -0,0 +1,82 @@ + +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package ar.edu.unlp.sedici.dspace.uploader; + +import org.apache.solr.common.SolrInputDocument; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.content.DSpaceObject; +import org.dspace.discovery.SolrServiceIndexPlugin; +import org.apache.log4j.Logger; + +/** + *

+ * Adds filenames and file descriptions of all files in the ORIGINAL bundle + * to the Solr search index. + * + *

+ * To activate the plugin, add the following line to discovery.xml + *

+ * {@code }
+ * 
+ * + *

+ * After activating the plugin, rebuild the discovery index by executing: + *

+ * [dspace]/bin/dspace index-discovery -b
+ * 
+ * + */ + + +public class SolrServiceYoutubeBitstreamPlugin implements SolrServiceIndexPlugin{ + private static final String BUNDLE_NAME = "ORIGINAL"; + private static final String SOLR_FIELD_NAME_FOR_YOUTUBEID = "original_bundle_youtubeid"; + private static final Logger log = Logger.getLogger(VideoUploaderServiceImpl.class); + + + @Override + public void additionalIndex(Context context, DSpaceObject dso, SolrInputDocument document) { + try{ + if (dso instanceof Item) { + /*Bitstream bitstream = ((Bitstream) dso); + Bundle[] bundlesi = null; + Bundle[] bundlesb = bitstream.getBundles(); + for (Bundle bundle : bundlesb){ + if((bundle.getName() != null) && bundle.getName().equals(BUNDLE_NAME)){ + Item[] item = bundle.getItems(); + bundlesi = item[0].getBundles(); + + } + }*/ + Item item= (Item) dso; + Bundle[] bundles = item.getBundles(); + if (bundles != null ){ + for (Bundle bundle:bundles){ + if((bundle.getName() != null) && bundle.getName().equals(BUNDLE_NAME)){ + Bitstream[] bitstreams = bundle.getBitstreams(); + if (bitstreams != null) { + for (Bitstream bstream : bitstreams) { + String youtubeId = bstream.getMetadata("sedici.identifier.youtubeId"); + if (youtubeId != null){ + document.addField(SOLR_FIELD_NAME_FOR_YOUTUBEID, youtubeId); + } + } + } + } + } + } + } + }catch(Exception e){ + log.error(e.getMessage(), e); + } + } +} diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/UploadExeption.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/UploadExeption.java new file mode 100644 index 000000000000..fd389d011c7c --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/UploadExeption.java @@ -0,0 +1,24 @@ +package ar.edu.unlp.sedici.dspace.uploader; + +import java.io.IOException; + +public class UploadExeption extends IOException { + + private Boolean resumable = false; + + public UploadExeption(String message, Boolean resumable) { + super(message); + this.resumable = resumable; + } + + public UploadExeption(String message,Boolean resumable, Throwable t) { + super(message, t); + this.resumable = resumable; + } + + public Boolean isResumable() { + return resumable; + } + + +} \ No newline at end of file diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderEventConsumer.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderEventConsumer.java new file mode 100644 index 000000000000..7a7a1ff1ab83 --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderEventConsumer.java @@ -0,0 +1,212 @@ +package ar.edu.unlp.sedici.dspace.uploader; + +import static org.dspace.event.Event.ADD; +import static org.dspace.event.Event.CREATE; +import static org.dspace.event.Event.DELETE; +import static org.dspace.event.Event.INSTALL; +import static org.dspace.event.Event.MODIFY; +import static org.dspace.event.Event.MODIFY_METADATA; +import static org.dspace.event.Event.REMOVE; + +import java.io.IOException; +import java.sql.SQLException; + +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Constants; + + +import org.apache.log4j.Logger; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.event.Consumer; +import org.dspace.event.Event; +import org.dspace.utils.DSpace; +import org.dspace.authorize.AuthorizeManager; + + +import com.google.api.services.youtube.YouTube; + +import org.dspace.curate.Curator; + +/** + * Event listener that filters events triggered by items with video bitstreams. + * Upload = ADD item to collection(publish the item), or ADD bitstream to bundle(allready published). + * Update = MODIFY_METADATA of item. + * Delete = REMOVE bitstream of bundle or bundle of item(last bitstream of the bundle) +*/ +public class VideoUploaderEventConsumer implements Consumer { + + /** + * log4j logger + */ + private static Logger log = Logger.getLogger(VideoUploaderEventConsumer.class); + + private final String MPEG_MIME_TYPE = "video/mpeg"; + private final String QUICKTIME_MIME_TYPE = "video/quicktime"; + private final String MP4_MIME_TYPE = "video/mp4"; + private final String QUEUE = "replicateVideo"; + + private final String REPLICATION_BUNDLE = "REPLICATION"; + private final String REPLICATION_METADATA = ConfigurationManager.getProperty("upload","video.identifier.metadata"); + + DSpace dspace = new DSpace(); + + ContentUploaderService uploader = dspace.getServiceManager().getServiceByName(ContentUploaderService.class.getName(),ContentUploaderService.class); + + @Override + public void initialize() throws Exception { + + } + + @Override + public void consume(Context ctx, Event event) { + int evType = event.getEventType(); + int st = event.getSubjectType(); + if (!(st == Constants.ITEM || st == Constants.BUNDLE || st == Constants.COLLECTION || st == Constants.BITSTREAM)) { + log.warn("VideoUploaderConsumer should not have been given this kind of Subject in an event, skipping: " + event.toString()); + return; + } + try { + switch (evType){ + case ADD: + if(st==Constants.COLLECTION){ + Item item = (Item) event.getObject(ctx); + Bundle[] bundles = item.getBundles("ORIGINAL"); + + Bitstream[] bitstreams = bundles[0].getBitstreams(); + + + if((item.getHandle() != null)){ + String mimeType; + for (Bitstream bitstream : bitstreams) { + mimeType = bitstream.getFormat().getMIMEType(); + if ((mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE))) { + if(AuthorizeManager.authorizeActionBoolean(ctx, bitstream, 0, false)) { + Curator curator = new Curator(); + curator.addTask("VideoUploaderTask").queue(ctx,item.getHandle(),QUEUE); + break; + }else { + log.info("El bitstream con id "+bitstream.getID()+" no esta autorizado para subirse"); + } + } + + } + } + + }else{ + if(st==Constants.BUNDLE && ((Bundle) event.getSubject(ctx)).getName().equals("ORIGINAL")){ + Bundle bundle = (Bundle) event.getSubject(ctx); + Item[] items = bundle.getItems(); + + String hdl = items[0].getHandle(); + Bitstream[] bitstreams = bundle.getBitstreams(); + + //Checks if published. + if(hdl != null){ + String mimeType; + for (Bitstream bitstream : bitstreams) { + mimeType = bitstream.getFormat().getMIMEType(); + //Checks that the bitstream is a video and that it is not uploaded on an external service in order to upload it. + if ((mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE))&&(bitstream.getMetadata(REPLICATION_METADATA) == null)) { + if(AuthorizeManager.authorizeActionBoolean(ctx, bitstream, 0, false)) { + Curator curator = new Curator(); + curator.addTask("VideoUploaderTask").queue(ctx,hdl,QUEUE); + break; + }else { + log.info("The bitstream with ID "+bitstream.getID()+" is not autorized to upload"); + } + } + } + } + } + } + break; + case MODIFY_METADATA: + if (st==Constants.ITEM) { + if(((Item) event.getSubject(ctx)).getHandle() != null){ + if(shouldUpdateMetadata(event)){ + if(st==Constants.ITEM){ + Item item = (Item) event.getSubject(ctx); + Bundle[] bundles = item.getBundles("ORIGINAL"); + Bitstream[] bitstreams = bundles[0].getBitstreams(); + String mimeType; + for (Bitstream bitstream : bitstreams) { + mimeType = bitstream.getFormat().getMIMEType(); + //Checks if the bitstream is a video and is published on an external service. + if ((mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE))&&(bitstream.getMetadata(REPLICATION_METADATA) != null)) { + Curator curator = new Curator(); + curator.addTask("VideoUpdaterTask").queue(ctx,item.getHandle(),QUEUE); + break; + } + } + } + } + } + }else if (st == Constants.BITSTREAM) { + Bitstream bitstream = (Bitstream) event.getSubject(ctx); + String mimeType = bitstream.getFormat().getMIMEType(); + //Checks if the bitstream is a video and is published on an external service. + if ((mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE))&&(bitstream.getMetadata(REPLICATION_METADATA) != null)) { + if(event.getDetail().contains("dc.description")) { + Curator curator = new Curator(); + curator.addTask("VideoUpdaterTask").queue(ctx,bitstream.getParentObject().getHandle(),QUEUE); + } + } + } + break; + case REMOVE: + if(st==Constants.BUNDLE ){ + Bundle bundle = (Bundle) event.getSubject(ctx); + //If the bitstream eliminated is not the last one of the bundle, you can get the bundle + if(bundle != null) { + Item item = (Item) bundle.getParentObject(); + if(item.getBundles(REPLICATION_BUNDLE).length > 0){ + Curator curator = new Curator(); + curator.addTask("VideoDeleteTask").queue(ctx,item.getHandle(),QUEUE); + } + } + //If the bitstream eliminated is the last one of the bundle, you want the event that deletes the bundle from the item + }else if (st==Constants.ITEM) { + Item item = (Item) event.getSubject(ctx); + if(item.getBundles(REPLICATION_BUNDLE).length > 0){ + Curator curator = new Curator(); + curator.addTask("VideoDeleteTask").queue(ctx,item.getHandle(),QUEUE); + } + } + break; + + } + }catch(SQLException e){ + log.error("SQLException: "+e.getMessage(),e); + } catch (IOException e) { + log.error("IOException: "+e.getMessage(),e); + } + + //} + } + + @Override + public void end(Context ctx) throws Exception { + + } + + @Override + public void finish(Context ctx) throws Exception { + + } + + /* + * Determines if at least one of the metadata used to build the description has been modified + */ + private boolean shouldUpdateMetadata(Event event) { + return ((event.getDetail().contains("dc.title")) || (event.getDetail().contains("dc.description.abstract")) + || (event.getDetail().contains("sedici.creator.person")) || (event.getDetail().contains("sedici.subtype")) + || (event.getDetail().contains("dc.date.available")) || (event.getDetail().contains("dc.identifier.uri")) + || (event.getDetail().contains("sedici.rights.license")) || (event.getDetail().contains("dc.subject")) + ); + } + +} diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderServiceImpl.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderServiceImpl.java new file mode 100644 index 000000000000..54296b39ac6d --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/VideoUploaderServiceImpl.java @@ -0,0 +1,337 @@ +package ar.edu.unlp.sedici.dspace.uploader; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.HashMap; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.Throwable; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; + +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.Metadatum; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Context; +import org.dspace.curate.Curator; +import org.dspace.eperson.EPerson; +import org.dspace.utils.DSpace; +import org.jsoup.Jsoup; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.stereotype.Service; + +import ar.edu.unlp.sedici.dspace.uploader.adapter.Adapter; +import ar.edu.unlp.sedici.util.MailReporter; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.AuthorizeManager; + + +@Service +public class VideoUploaderServiceImpl implements ContentUploaderService{ + + private static final Logger log = Logger.getLogger(VideoUploaderServiceImpl.class); + + private final String MPEG_MIME_TYPE = "video/mpeg"; + private final String QUICKTIME_MIME_TYPE = "video/quicktime"; + private final String MP4_MIME_TYPE = "video/mp4"; + + private final String REPLICATION_BUNDLE = "REPLICATION"; + + private Adapter adapter; + + public VideoUploaderServiceImpl(Adapter adapter) { + super(); + this.adapter = adapter; + } + + @Override + public void uploadContent(Item item) throws IOException { + + String handle = item.getHandle(); + log.info("Starting the upload for the item with handle " + handle +" to external service"); + String itemTitle= Jsoup.parse(item.getMetadata("dc.title")).text(); + try { + //Create the Bundle if necessary + if (item.getBundles(REPLICATION_BUNDLE).length==0) { + item.createBundle(REPLICATION_BUNDLE); + } + + Bitstream[] bitstreams = item.getBundles("ORIGINAL")[0].getBitstreams(); + + for (Bitstream bitstream : bitstreams) { + + if (autorizarSubida(bitstream)) { + String title; + if(bitstream.getMetadata("dc.description").isEmpty()) { + title = itemTitle; + log.warn("The bitstream with ID "+bitstream.getID()+" is going to be uploaded to an external service, but it has no description, and the title will be "+title); + } else { + title = itemTitle+" - "+bitstream.getMetadata("dc.description"); + } + String videoID = adapter.uploadVideo(bitstream.retrieve(), title, this.buildMetadata(item), this.buildTags(item)); + if(videoID != null) { + log.info("The upload for the bitstream with ID "+bitstream.getID()+",contained in the item with handle "+handle+", finished successfully, uploading the video with new ID "+videoID); + persistirId(videoID,item,bitstream); + } + } + } + }catch(UploadExeption e){ + //log.error(e.getMessage()); the Adapter is responsible for the error log in this case + this.resolveExeption(e,"upload",item); + } catch (SQLException e) { + log.error("SQLException: "+e.getMessage(),e); + throw new IOException(e); + } catch (AuthorizeException e) { + log.error("AuthorizeException: "+e.getMessage(),e); + throw new IOException(e); + } + + } + + private boolean autorizarSubida(Bitstream bitstream) throws UploadExeption, SQLException{ + Context ctx = new Context(); + String[] schemaL = this.getReplicationMetadataName().split("\\."); + if(AuthorizeManager.authorizeActionBoolean(ctx, bitstream, 0, false)) { + ctx.complete(); + String mimeType = bitstream.getFormat().getMIMEType(); + if (mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE)) { + List replicationId = bitstream.getMetadata(schemaL[0],schemaL[1],schemaL[2],Item.ANY,Item.ANY); + if (replicationId.size() == 0) { + return true; + }else { + log.info("The bitstream with ID "+bitstream.getID()+" is allready replicated in the external service "+bitstream.getMetadata(this.getReplicationMetadataName())); + } + } + } else { + ctx.complete(); + log.info("The bitstream with ID "+bitstream.getID()+" shouldn't be replicated to an external service due to it not being accessible to anonymous user"); + } + return false; + } + + private void persistirId(String id, Item item,Bitstream bitstream) { + String[] schemaL = this.getReplicationMetadataName().split("\\."); + String lang = null; + bitstream.addMetadata(schemaL[0],schemaL[1],schemaL[2],lang,id); + try { + bitstream.updateMetadata(); + item.updateLastModified(); + String initialString = bitstream.getID()+";"+id; + InputStream targetStream = new ByteArrayInputStream(initialString.getBytes()); + Bundle bundle = item.getBundles(REPLICATION_BUNDLE)[0]; + bundle.createBitstream(targetStream).setName("Map bitstream - external service"); + bundle.update(); + } catch (SQLException e) { + log.error("SQLException: " + e.getMessage()); + } catch (AuthorizeException e) { + log.error("AuthorizeException: " + e.getMessage()); + } catch (IOException e) { + log.error("IOException: " + e.getMessage()); + } + } + + @Override + public void removeContent(Item item) throws IOException { + try { + if(item.getBundles(REPLICATION_BUNDLE).length != 0) { + Bitstream[] mapsReplication = item.getBundles(REPLICATION_BUNDLE)[0].getBitstreams(); + if(item.getBundles("ORIGINAL").length>0) { + for (Bitstream map : mapsReplication) { + String idVideo = determinarBorradoDeBitstream(map,item); + if (idVideo != null) { + adapter.deleteVideo(idVideo); + item.getBundles(REPLICATION_BUNDLE)[0].removeBitstream(map); + } + } + }else { + /** + * If you remove the last item in the bundle ORIGINAL, it removes the bundle from the item. + * In this case, if the bundle REPLICATION exists, you have to eliminate every video in this bundle + */ + for (Bitstream map : mapsReplication) { + String[] mapeo = parsearBitstream(map); + adapter.deleteVideo(mapeo[1]); + item.getBundles(REPLICATION_BUNDLE)[0].removeBitstream(map); + } + + } + } + }catch(UploadExeption e){ + //log.error(e.getMessage()); Adapter is responsible for the error log in this case + this.resolveExeption(e,"delete",item); + } catch (SQLException e) { + log.error("SQLException: "+e.getMessage(),e); + throw new IOException(e); + } catch (AuthorizeException e) { + log.error("AuthorizeException: "+e.getMessage(),e); + throw new IOException(e); + } + + } + + /** + * Transform the relation between bitstream ID and the external service ID, to a list of Strings + * @return String list [0] Bitstream ID [1] external service ID + */ + private String[] parsearBitstream(Bitstream bitstream) throws IOException, SQLException, AuthorizeException { + String data = IOUtils.toString(bitstream.retrieve(), StandardCharsets.UTF_8); + return data.toString().split(";"); + } + + private String determinarBorradoDeBitstream(Bitstream relacionBY, Item item) throws IOException, SQLException, AuthorizeException { + Bitstream[] bitstreams = item.getBundles("ORIGINAL")[0].getBitstreams(); + String[] listaIDs = parsearBitstream(relacionBY); + Boolean existe = false; + for (Bitstream bitstream : bitstreams) { + //Checks if the bitstream replicated in an external site has been deleted + if(listaIDs[0].equals(Integer.toString(bitstream.getID())) ) { + existe = true; + } + } + if (!existe){ + return listaIDs[1]; + } + else { + return null; + } + } + + @Override + public void modifyContent(Item item) throws IOException { + String handle = item.getHandle(); + log.info("Update of item " + handle +" to an external site"); + String itemTitle= Jsoup.parse(item.getMetadata("dc.title")).text(); + try { + + Bitstream[] bitstreams = item.getBundles("ORIGINAL")[0].getBitstreams(); + + for (Bitstream bitstream : bitstreams) { + if(autorizarModificacion(bitstream)) { + String title = itemTitle+" - "+bitstream.getMetadata("dc.description"); + String videoID = adapter.updateMetadata(bitstream.getMetadata(this.getReplicationMetadataName()), title, this.buildMetadata(item), this.buildTags(item)); + log.info("The video whit ID "+videoID+" and title '"+title+"' has been updated"); + } + } + }catch(UploadExeption e){ + //log.error(e.getMessage()); Adapter is responsible for the error log in this case + this.resolveExeption(e,"update",item); + }catch(SQLException e) { + log.error("SQLException: "+e.getMessage(),e); + throw new IOException(e); + } + + } + + private boolean autorizarModificacion(Bitstream bitstream) { + String mimeType = bitstream.getFormat().getMIMEType(); + if (mimeType.equalsIgnoreCase(MP4_MIME_TYPE) | mimeType.equalsIgnoreCase(MPEG_MIME_TYPE) | mimeType.equalsIgnoreCase(QUICKTIME_MIME_TYPE)) { + if (bitstream.getMetadata(this.getReplicationMetadataName()) != null) { + return true; + } + } + return false; + } + + private Map buildMetadata(Item item) { + + Map metadata = new HashMap(); + + //metadata.put("title", Jsoup.parse(item.getMetadata("dc.title")).text()); + metadata.put("creators", item.getMetadata("sedici","creator","person",Item.ANY,Item.ANY)); + metadata.put("subtype", item.getMetadata("sedici.subtype")); + //metadata.put("dateAvailable", item.getMetadata("dc.date.available")); + metadata.put("iUri", item.getMetadata("dc.identifier.uri")); + metadata.put("language", item.getMetadata("dc.language")); + metadata.put("subjects", item.getMetadata("dc","subject",Item.ANY,Item.ANY,Item.ANY)); + if(item.getMetadata("dc.description.abstract") != null){ + metadata.put("abstract", Jsoup.parse(item.getMetadata("dc.description.abstract")).text()); + } + metadata.put("license", item.getMetadata("sedici.rights.license")); + return metadata; + } + + + private List buildTags(Item item){ + List tags = new ArrayList(); + tags.add("UNLP"); + + ListIterator palabras = item.getMetadata("dc","subject",Item.ANY,Item.ANY,Item.ANY).listIterator(); + while (palabras.hasNext()) { + tags.add(palabras.next().value); + } + + ListIterator origen = item.getMetadata("mods","originInfo","place",Item.ANY,Item.ANY).listIterator(); + while (origen.hasNext()) { + tags.add(origen.next().value); + } + + ListIterator materias = item.getMetadata("sedici","subject","materias",Item.ANY,Item.ANY).listIterator(); + while (materias.hasNext()) { + tags.add(materias.next().value); + } + + ListIterator catedras = item.getMetadata("sedici","description","catedra",Item.ANY,Item.ANY).listIterator(); + while (catedras.hasNext()) { + tags.add(catedras.next().value); + } + return tags; + } + + private void resolveExeption(UploadExeption e, String contexto, Item item){ + try { + if(e.isResumable()) { + Curator curator = new Curator(); + Context ctx = new Context(); + ctx.turnOffAuthorisationSystem(); + ctx.setCurrentUser(EPerson.findByEmail(ctx, ConfigurationManager.getProperty("upload","upload.user")));//Crear y usar el usuario info+video@sedici.unlp.edu.ar + String task; + if (contexto.equals("upload")) { + task = "VideoUploaderTask"; + }else if(contexto.equals("update")){ + task = "VideoUpdaterTask"; + }else { + task = "VideoDeleteTask"; + } + curator.addTask(task).queue(ctx,item.getHandle(),"replicateVideo"); + ctx.complete(); + } + if(!e.isResumable()) { + MailReporter.reportUnknownException("Ocurrio un error no reasumible en el "+ contexto +" del item con handle "+item.getHandle(), e, "http://sedici.unlp.edu.ar/handle/"+item.getHandle()); + }else if (e.getMessage().equals("The daily quota of Youtube has exeded")){ + MailReporter.reportUnknownException("Ocurrio un error reasumible en el "+ contexto +" del item con handle "+item.getHandle()+". Se agoto la quota de Youtube", e, "http://sedici.unlp.edu.ar/handle/"+item.getHandle()); + }else if (e.getMessage().equals("No quota")){ + /** + * In this case, you don't have to send an email, as the email indicating + * that the quota limit has already been reached has been sent + * This case exist to re-queue the curation tasks + */ + }else { + MailReporter.reportUnknownException("An unhandled error as ocurred in the "+ contexto +" del item con handle "+item.getHandle(), e, "http://sedici.unlp.edu.ar/handle/"+item.getHandle()); + } + } catch (Throwable t) { + log.error("Error during exeption management in the VideoUploaderService"); + log.error("Throwable: " + t.getMessage()); + } + } + + private String getReplicationMetadataName() { + return ConfigurationManager.getProperty("upload","video.identifier.metadata"); + } +} diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/Adapter.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/Adapter.java new file mode 100644 index 000000000000..cd97a62341ad --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/Adapter.java @@ -0,0 +1,17 @@ +package ar.edu.unlp.sedici.dspace.uploader.adapter; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import ar.edu.unlp.sedici.dspace.uploader.UploadExeption; + +public interface Adapter { + + public String uploadVideo(InputStream videoFile, final String title, Map metadata, List tags) throws UploadExeption; + + public String updateMetadata(String videoId, String title, Map metadata, List tags) throws UploadExeption; + + public String deleteVideo(String videoId) throws UploadExeption; + +} diff --git a/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/YoutubeAdapter.java b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/YoutubeAdapter.java new file mode 100644 index 000000000000..0a7f4ce986ea --- /dev/null +++ b/dspace/modules/additions/src/main/java/ar/edu/unlp/sedici/dspace/uploader/adapter/YoutubeAdapter.java @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2012 Google Inc. + * + * 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 ar.edu.unlp.sedici.dspace.uploader.adapter; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.HashMap; + +import org.apache.log4j.Logger; +import org.json.JSONObject; + +import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.auth.oauth2.TokenResponse; +import com.google.api.client.auth.oauth2.TokenResponseException; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.java6.auth.oauth2.FileCredentialStore; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.googleapis.media.MediaHttpUploader; +import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.InputStreamContent; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.youtube.YouTube; +import com.google.api.services.youtube.YouTube.VideoCategories; +import com.google.api.services.youtube.model.Video; +import com.google.api.services.youtube.model.VideoCategory; +import com.google.api.services.youtube.model.VideoListResponse; +import com.google.api.services.youtube.model.VideoSnippet; +import com.google.api.services.youtube.model.VideoStatus; +import com.google.common.collect.Lists; + +import ar.edu.unlp.sedici.dspace.uploader.UploadExeption; + +import org.dspace.content.Metadatum; +import org.dspace.core.ConfigurationManager; +import org.springframework.stereotype.Service; + +/** + * The service responsible for communication with YouTube, specifically handling the uploading, + * updating, and deletion of videos. For all these operations, it also takes care of authorization. + * Any code related to YouTube should be found here and abstracted from DSpace + * regarding the communication details + */ + +@Service +public class YoutubeAdapter implements Adapter{ + + public YoutubeAdapter() { + super(); + } + + static final Logger logger = Logger.getLogger(YoutubeAdapter.class); + + /** Global instance of the HTTP transport. */ + private final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); + + /** Global instance of the JSON factory. */ + private final JsonFactory JSON_FACTORY = new JacksonFactory(); + + /** Global instance of Youtube object to make all API requests. */ + private YouTube youtube; + + /* + * Global instance of the format used for the video being uploaded (MIME type). + */ + private String VIDEO_FILE_FORMAT = "video/*"; + + private Credential CREDENTIAL; + + private boolean noQuota = false; + + /** + * Authorizes the installed application to access user's protected data. + * + * @param scopes list of scopes needed to run youtube upload. + * @throws IOException + */ + private void authorize(List scopes) throws IOException { + + // Load client secrets. + Reader reader = new InputStreamReader(new FileInputStream(new File(ConfigurationManager.getProperty("youtube.upload","youtube.upload.secrets")))); + GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, reader); + + // Set up file credential store. + FileCredentialStore credentialStore = new FileCredentialStore( + new File(ConfigurationManager.getProperty("upload","youtube.upload.refresh")), JSON_FACTORY); + + // Set up authorization code flow. + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, + clientSecrets, scopes).setCredentialStore(credentialStore).setAccessType("offline").build(); + + this.CREDENTIAL = flow.loadCredential(clientSecrets.getDetails().getClientId()); + /** + * Se verifica que si se tiene el refresh token y no esta expirado el acces token, se lo devuelve + * Caso contrario se debe de volver a autorizar y refrescar el acces token + */ + if (CREDENTIAL != null + && (CREDENTIAL.getRefreshToken() != null + || CREDENTIAL.getExpiresInSeconds() == null + || CREDENTIAL.getExpiresInSeconds() > 60)) { + return; + } + + // open in browser + String redirectUri = ConfigurationManager.getProperty("upload.redirect_uri"); + AuthorizationCodeRequestUrl authorizationUrl = + flow.newAuthorizationUrl().setRedirectUri(redirectUri); + + // receive authorization code and exchange it for an access token + System.out.println(authorizationUrl.build()); + System.out.print("Please enter code: "); + String code = new Scanner(System.in).nextLine(); + TokenResponse response = flow.newTokenRequest(code).setRedirectUri(redirectUri).execute(); + + // store credential and acces token + CREDENTIAL = flow.createAndStoreCredential(response, clientSecrets.getDetails().getClientId()); + + } + + /** + * Uploads video to the user's YouTube account using OAuth2 for authentication. + * + * @param videoFile + * @return String Youtube ID + */ + public String uploadVideo(InputStream videoFile, final String title, Map metadata, List tags) throws UploadExeption { + if (noQuota) { + throw new UploadExeption("No quota",true); + } + // Scope required to upload to YouTube. + List scopes = Lists.newArrayList("https://www.googleapis.com/auth/youtube.force-ssl"); + try { + // Authorization. + if ((CREDENTIAL == null)|| + (CREDENTIAL.getAccessToken() == null)){ + authorize(scopes); + } + //Credential credential = authorize(scopes); + + // YouTube object used to make all API requests. + youtube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, this.CREDENTIAL) + .setApplicationName("DSpace SEDICI").build(); + + List categories = new ArrayList(); + categories.add("snippet"); + // Add extra information to the video before uploading. + Video videoObjectDefiningMetadata = new Video(); + + /** + * Set the video to public, so it is available to everyone (what most people + * want). This is actually the default, but I wanted you to see what it looked + * like in case you need to set it to "unlisted" or "private" via API. + */ + VideoStatus status = new VideoStatus(); + status.setLicense("creativeCommon"); + status.setSelfDeclaredMadeForKids(false); + status.setMadeForKids(false); + status.setPrivacyStatus(ConfigurationManager.getProperty("upload","youtube.upload.video.state")); + videoObjectDefiningMetadata.setStatus(status); + + // We set a majority of the metadata with the VideoSnippet object. + VideoSnippet snippet = new VideoSnippet(); + + snippet.setTitle(title); + String description = buildDescription(metadata); + snippet.setDescription(description); + + // Set the category of your video (allways Education) + snippet.setCategoryId(this.getEducationId()); + + // Set your keywords. + snippet.setTags(tags); + snippet.setDefaultLanguage((String)metadata.get("language")); + + // Set completed snippet to the video object. + videoObjectDefiningMetadata.setSnippet(snippet); + + InputStreamContent mediaContent = new InputStreamContent(VIDEO_FILE_FORMAT, + new BufferedInputStream(videoFile)); + + /** + * The upload command includes: 1. Information we want returned after file is + * successfully uploaded. 2. Metadata we want associated with the uploaded + * video. 3. Video file itself. + */ + List list = new ArrayList(); + list.add("snippet"); + list.add("statistics"); + list.add("status"); + YouTube.Videos.Insert videoInsert = youtube.videos().insert(list, + videoObjectDefiningMetadata, mediaContent); + + // Set the upload type and add event listener. + MediaHttpUploader uploader = videoInsert.getMediaHttpUploader(); + + /** + * Sets whether direct media upload is enabled or disabled. True = whole media + * content is uploaded in a single request. False (default) = resumable media + * upload protocol to upload in data chunks. + */ + uploader.setDirectUploadEnabled(false); + + MediaHttpUploaderProgressListener progressListener = new MediaHttpUploaderProgressListener() { + public void progressChanged(MediaHttpUploader uploader) throws IOException { + switch (uploader.getUploadState()) { + case INITIATION_STARTED: + logger.trace("Starting the upload for the video with the title '"+ title+"'"); + break; + case INITIATION_COMPLETE: + logger.trace("Initiation Completed"); + break; + case MEDIA_COMPLETE: + logger.trace("The upload with title: '"+ title +"' has finished"); + break; + case MEDIA_IN_PROGRESS: + logger.trace("Upload in progress"); + logger.trace("Upload percentage: " + uploader.getProgress()); + break; + case NOT_STARTED: + logger.trace("Upload Not Started!"); + break; + } + } + }; + uploader.setProgressListener(progressListener); + + // Execute upload. + Video returnedVideo = videoInsert.execute(); + return returnedVideo.getId(); + }catch (IOException e) { + throw manageException(e); + } + } + + public String updateMetadata(String videoId, String title, Map metadata, List tags) throws UploadExeption{ + if (noQuota) { + throw new UploadExeption("No quota",true); + } + List scopes = Lists.newArrayList("https://www.googleapis.com/auth/youtube.force-ssl"); + try { + // Authorization. + if ((CREDENTIAL == null)|| + (CREDENTIAL.getAccessToken() == null)){ + authorize(scopes); + } + + // YouTube object used to make all API requests. + youtube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY,this.CREDENTIAL). + setApplicationName("DSpace SEDICI").build(); + + List parts = new ArrayList(); + parts.add("snippet"); + List lvideoId = new ArrayList(); + lvideoId.add(videoId); + + // Create the video list request + YouTube.Videos.List listVideosRequest = youtube.videos().list(parts).setId(lvideoId); + + // Request is executed and video list response is returned + VideoListResponse listResponse = listVideosRequest.execute(); + + List