diff --git a/openvidu-server/docker/openvidu-docker-compose/docker-compose.yml b/openvidu-server/docker/openvidu-docker-compose/docker-compose.yml index b74285c9..25b61a87 100644 --- a/openvidu-server/docker/openvidu-docker-compose/docker-compose.yml +++ b/openvidu-server/docker/openvidu-docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ version: '3.1' services: openvidu-server: - image: openvidu/openvidu-server:2.13.0-beta4 + image: openvidu/openvidu-server:2.13.0-beta3 restart: on-failure network_mode: host volumes: diff --git a/openvidu-server/docker/openvidu-docker-compose/readme.md b/openvidu-server/docker/openvidu-docker-compose/readme.md index 9de7a44b..c1ccee3e 100644 --- a/openvidu-server/docker/openvidu-docker-compose/readme.md +++ b/openvidu-server/docker/openvidu-docker-compose/readme.md @@ -41,9 +41,7 @@ First clone this repository and move to openvidu-docker-compose folder: ``` $ git clone https://github.com/OpenVidu/openvidu.git -$ cd openvidu -$ git checkout -b deploy-docker-compose origin/deploy-docker-compose -$ cd openvidu-server/docker/openvidu-docker-compose +$ cd openvidu/openvidu-server/docker/openvidu-docker-compose ``` ### OpenVidu configuration diff --git a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java index fd4cb5f5..64407141 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java +++ b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java @@ -242,12 +242,16 @@ public class OpenViduServer implements JsonRpcConfigurer { String msg = "\n\n\n" + " Configuration errors\n" + " --------------------\n" + "\n"; for (Error error : config.getConfigErrors()) { - msg += " * Property " + config.getPropertyName(error.getProperty()); - if (error.getValue() == null || error.getValue().equals("")) { - msg += " is not set. "; - } else { - msg += "=" + error.getValue() + ". "; + msg += " * "; + if(error.getProperty() != null) { + msg += "Property " + config.getPropertyName(error.getProperty()); + if (error.getValue() == null || error.getValue().equals("")) { + msg += " is not set. "; + } else { + msg += "=" + error.getValue() + ". "; + } } + msg += error.getMessage() + "\n"; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/Dotenv.java b/openvidu-server/src/main/java/io/openvidu/server/config/Dotenv.java new file mode 100644 index 00000000..b5ff822a --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/config/Dotenv.java @@ -0,0 +1,147 @@ +package io.openvidu.server.config; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Dotenv { + + public static class DotenvFormatException extends Exception { + + private static final long serialVersionUID = -7280645547648990756L; + + public DotenvFormatException(String msg) { + super(msg); + } + } + + private static Logger log = LoggerFactory.getLogger(Dotenv.class); + + private List lines; + private Map properties; + + private Path envFile; + + private List additionalProperties = new ArrayList<>(); + + public void read() throws IOException, DotenvFormatException { + read(Paths.get(".env")); + } + + public String get(String property) { + return properties.get(property); + } + + public Path getEnvFile() { + return envFile; + } + + public void write() throws IOException { + write(envFile); + } + + public void write(Path envFile) throws IOException { + + List outLines = new ArrayList<>(); + + for (String line : lines) { + + try { + + Pair propValue = parseLine(envFile, line); + if(propValue == null) { + outLines.add(line); + } else { + outLines.add(propValue.getKey()+"="+properties.get(propValue.getKey())); + } + + } catch (DotenvFormatException e) { + log.error("Previously parsed line is producing a parser error", e); + } + } + + if(!additionalProperties.isEmpty()) { + for(String prop : additionalProperties) { + outLines.add(prop+"="+properties.get(prop)); + } + } + + Files.write(envFile, outLines, Charset.defaultCharset(), StandardOpenOption.CREATE); + + } + + public void read(Path envFile) throws IOException, DotenvFormatException { + + this.envFile = envFile; + + lines = Files.readAllLines(envFile); + + properties = new HashMap<>(); + for (String line : lines) { + + log.debug("Reading line '{}'", line); + + Pair propValue = parseLine(envFile, line); + + if (propValue != null) { + + log.debug("Setting property {}={}", propValue.getKey(), propValue.getValue()); + + properties.put(propValue.getKey(), propValue.getValue()); + } + } + } + + private Pair parseLine(Path envFile, String line) throws DotenvFormatException { + + if (isWhitespace(line) || isComment(line)) { + return null; + } + + int index = line.indexOf("="); + + if (index == -1) { + throw new DotenvFormatException("File " + envFile + " has a malformed line with content \"" + line + "\""); + } + + String property = line.substring(0, index).trim(); + + if (property.equals("")) { + throw new DotenvFormatException("File " + envFile + " has a malformed line with content \"" + line + "\""); + } + + String value = line.substring(index + 1); + + return ImmutablePair.of(property, value); + } + + private boolean isComment(String line) { + return line.startsWith("#") || line.startsWith("//"); + } + + private boolean isWhitespace(String line) { + return line.trim().equals(""); + } + + public void set(String property, String value) { + + if (!properties.containsKey(property)) { + additionalProperties.add(property); + } + + this.properties.put(property, value); + } + +} diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java index deca9b0e..ad94b3fc 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java @@ -18,6 +18,7 @@ package io.openvidu.server.config; import java.io.File; +import java.io.IOException; import java.net.Inet6Address; import java.net.MalformedURLException; import java.net.URI; @@ -50,6 +51,7 @@ import com.google.gson.JsonSyntaxException; import io.openvidu.java.client.OpenViduRole; import io.openvidu.server.OpenViduServer; import io.openvidu.server.cdr.CDREventName; +import io.openvidu.server.config.Dotenv.DotenvFormatException; import io.openvidu.server.recording.RecordingNotification; @Component @@ -96,6 +98,8 @@ public class OpenviduConfig { @Autowired protected Environment env; + protected Dotenv dotenv; + @Value("#{'${spring.profiles.active:}'.length() > 0 ? '${spring.profiles.active:}'.split(',') : \"default\"}") protected String springProfile; @@ -371,7 +375,17 @@ public class OpenviduConfig { @PostConstruct protected void checkConfigurationProperties() { - + + dotenv = new Dotenv(); + + try { + dotenv.read(); + } catch (IOException e) { + log.warn("Exception reading .env file. "+ e.getClass()+":"+e.getMessage()); + } catch (DotenvFormatException e) { + log.warn("Format error in .env file. "+ e.getClass()+":"+e.getMessage()); + } + try { this.checkConfigurationParameters(); } catch (Exception e) { @@ -556,8 +570,15 @@ public class OpenviduConfig { } public List checkKmsUris() { + String property = "kms.uris"; - String kmsUris = getConfigValue(property); + + return asKmsUris(property, getConfigValue(property)); + + } + + public List asKmsUris(String property, String kmsUris) { + if (kmsUris == null || kmsUris.isEmpty()) { return Arrays.asList(); } @@ -577,7 +598,6 @@ public class OpenviduConfig { addError(property, uri + " is not a valid WebSocket URL"); } } - return kmsUrisArray; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java index c8e66519..7fedf892 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReadWriteLock; import java.util.function.Function; import org.apache.commons.lang3.RandomStringUtils; @@ -90,15 +91,6 @@ public class KurentoParticipant extends Participant { this.publisher = new PublisherEndpoint(endpointType, this, participant.getParticipantPublicId(), this.session.getPipeline(), this.openviduConfig, null); } - - for (Participant other : session.getParticipants()) { - if (!other.getParticipantPublicId().equals(this.getParticipantPublicId()) - && !OpenViduRole.SUBSCRIBER.equals(other.getToken().getRole())) { - // Initialize a SubscriberEndpoint for each other user connected with PUBLISHER - // or MODERATOR role - getNewOrExistingSubscriber(other.getParticipantPublicId()); - } - } } public void createPublishingEndpoint(MediaOptions mediaOptions, String streamId) { @@ -226,92 +218,126 @@ public class KurentoParticipant extends Participant { KurentoParticipant kSender = (KurentoParticipant) sender; - if (kSender.getPublisher() == null) { - log.warn("PARTICIPANT {}: Trying to connect to a user without a publishing endpoint", - this.getParticipantPublicId()); - return null; - } + if (kSender.streaming && kSender.getPublisher() != null + && kSender.getPublisher().closingLock.readLock().tryLock()) { - log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(), - senderName); - - SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName); - - try { - CountDownLatch subscriberLatch = new CountDownLatch(1); - Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch); try { - if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Timeout reached when creating subscriber endpoint"); + log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(), + senderName); + + SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName); + + try { + CountDownLatch subscriberLatch = new CountDownLatch(1); + Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch); + + try { + if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) { + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, + "Timeout reached when creating subscriber endpoint"); + } + } catch (InterruptedException e) { + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, + "Interrupted when creating subscriber endpoint: " + e.getMessage()); + } + if (oldMediaEndpoint != null) { + log.warn( + "PARTICIPANT {}: Two threads are trying to create at " + + "the same time a subscriber endpoint for user {}", + this.getParticipantPublicId(), senderName); + return null; + } + if (subscriber.getEndpoint() == null) { + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, + "Unable to create subscriber endpoint"); + } + + String subscriberEndpointName = this.getParticipantPublicId() + "_" + + kSender.getPublisherStreamId(); + + subscriber.setEndpointName(subscriberEndpointName); + subscriber.getEndpoint().setName(subscriberEndpointName); + subscriber.setStreamId(kSender.getPublisherStreamId()); + + endpointConfig.addEndpointListeners(subscriber, "subscriber"); + + } catch (OpenViduException e) { + this.subscribers.remove(senderName); + throw e; } - } catch (InterruptedException e) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Interrupted when creating subscriber endpoint: " + e.getMessage()); + + log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(), + senderName); + try { + String sdpAnswer = subscriber.subscribe(sdpOffer, kSender.getPublisher()); + log.trace("PARTICIPANT {}: Subscribing SdpAnswer is {}", this.getParticipantPublicId(), sdpAnswer); + log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(), + senderName, this.session.getSessionId()); + + if (!silent + && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) { + endpointConfig.getCdr().recordNewSubscriber(this, this.session.getSessionId(), + sender.getPublisherStreamId(), sender.getParticipantPublicId(), subscriber.createdAt()); + } + + return sdpAnswer; + } catch (KurentoServerException e) { + // TODO Check object status when KurentoClient sets this info in the object + if (e.getCode() == 40101) { + log.warn( + "Publisher endpoint was already released when trying to connect a subscriber endpoint to it", + e); + } else { + log.error("Exception connecting subscriber endpoint to publisher endpoint", e); + } + this.subscribers.remove(senderName); + releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false); + return null; + } + } finally { + kSender.getPublisher().closingLock.readLock().unlock(); } - if (oldMediaEndpoint != null) { - log.warn( - "PARTICIPANT {}: Two threads are trying to create at " - + "the same time a subscriber endpoint for user {}", - this.getParticipantPublicId(), senderName); - return null; - } - if (subscriber.getEndpoint() == null) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint"); - } - - String subscriberEndpointName = this.getParticipantPublicId() + "_" + kSender.getPublisherStreamId(); - - subscriber.setEndpointName(subscriberEndpointName); - subscriber.getEndpoint().setName(subscriberEndpointName); - subscriber.setStreamId(kSender.getPublisherStreamId()); - - endpointConfig.addEndpointListeners(subscriber, "subscriber"); - - } catch (OpenViduException e) { - this.subscribers.remove(senderName); - throw e; + } else { + log.error( + "PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ", + senderName, sender.getSessionId(), this.participantPublicId); + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, + "Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName + + "is closed"); } - - log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(), senderName); - try { - String sdpAnswer = subscriber.subscribe(sdpOffer, kSender.getPublisher()); - log.trace("PARTICIPANT {}: Subscribing SdpAnswer is {}", this.getParticipantPublicId(), sdpAnswer); - log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(), - senderName, this.session.getSessionId()); - - if (!silent && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) { - endpointConfig.getCdr().recordNewSubscriber(this, this.session.getSessionId(), - sender.getPublisherStreamId(), sender.getParticipantPublicId(), subscriber.createdAt()); - } - - return sdpAnswer; - } catch (KurentoServerException e) { - // TODO Check object status when KurentoClient sets this info in the object - if (e.getCode() == 40101) { - log.warn("Publisher endpoint was already released when trying " - + "to connect a subscriber endpoint to it", e); - } else { - log.error("Exception connecting subscriber endpoint " + "to publisher endpoint", e); - } - this.subscribers.remove(senderName); - releaseSubscriberEndpoint((KurentoParticipant) sender, subscriber, null, false); - } - return null; } public void cancelReceivingMedia(KurentoParticipant senderKurentoParticipant, EndReason reason, boolean silent) { final String senderName = senderKurentoParticipant.getParticipantPublicId(); - - log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(), senderName); - SubscriberEndpoint subscriberEndpoint = subscribers.remove(senderName); - if (subscriberEndpoint == null || subscriberEndpoint.getEndpoint() == null) { - log.warn("PARTICIPANT {}: Trying to cancel receiving video from user {}. " - + "But there is no such subscriber endpoint.", this.getParticipantPublicId(), senderName); - } else { - releaseSubscriberEndpoint(senderKurentoParticipant, subscriberEndpoint, reason, silent); - log.info("PARTICIPANT {}: stopped receiving media from {} in room {}", this.getParticipantPublicId(), - senderName, this.session.getSessionId()); + final PublisherEndpoint pub = senderKurentoParticipant.publisher; + if (pub != null) { + try { + if (pub.closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) { + try { + log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(), + senderName); + SubscriberEndpoint subscriberEndpoint = subscribers.remove(senderName); + if (subscriberEndpoint == null) { + log.warn( + "PARTICIPANT {}: Trying to cancel receiving video from user {}. " + + "But there is no such subscriber endpoint.", + this.getParticipantPublicId(), senderName); + } else { + releaseSubscriberEndpoint(senderName, senderKurentoParticipant, subscriberEndpoint, reason, + silent); + log.info("PARTICIPANT {}: stopped receiving media from {} in room {}", + this.getParticipantPublicId(), senderName, this.session.getSessionId()); + } + } finally { + pub.closingLock.writeLock().unlock(); + } + } + } catch (InterruptedException e) { + subscribers.remove(senderName); + log.error( + "Timeout wating for PublisherEndpoint closing lock of participant {} to be available for participant {} to call cancelReceveivingMedia", + senderName, this.getParticipantPublicId()); + } } } @@ -330,7 +356,7 @@ public class KurentoParticipant extends Participant { it.remove(); if (subscriber != null && subscriber.getEndpoint() != null) { - releaseSubscriberEndpoint( + releaseSubscriberEndpoint(remoteParticipantName, (KurentoParticipant) this.session.getParticipantByPublicId(remoteParticipantName), subscriber, reason, false); log.debug("PARTICIPANT {}: Released subscriber endpoint to {}", this.getParticipantPublicId(), @@ -367,7 +393,6 @@ public class KurentoParticipant extends Participant { log.debug("PARTICIPANT {}: New subscriber endpoint to user {}", this.getParticipantPublicId(), senderPublicId); } - return subscriberEndpoint; } @@ -391,52 +416,71 @@ public class KurentoParticipant extends Participant { private void releasePublisherEndpoint(EndReason reason, long kmsDisconnectionTime) { if (publisher != null && publisher.getEndpoint() != null) { - - // Remove streamId from publisher's map - this.session.publishedStreamIds.remove(this.getPublisherStreamId()); - - if (this.openviduConfig.isRecordingModuleEnabled() - && this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) { - this.recordingManager.stopOneIndividualStreamRecording(session, this.getPublisherStreamId(), - kmsDisconnectionTime); + final ReadWriteLock closingLock = publisher.closingLock; + try { + if (closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) { + try { + this.releasePublisherEndpointAux(reason, kmsDisconnectionTime); + } finally { + closingLock.writeLock().unlock(); + } + } + } catch (InterruptedException e) { + log.error( + "Timeout wating for PublisherEndpoint closing lock of participant {} to be available to call releasePublisherEndpoint", + this.participantPublicId, this.getParticipantPublicId()); + log.error("Forcing PublisherEndpoint release. Possibly some session event will be incomplete"); + this.releasePublisherEndpointAux(reason, kmsDisconnectionTime); } - - publisher.unregisterErrorListeners(); - publisher.cancelStatsLoop.set(true); - - for (MediaElement el : publisher.getMediaElements()) { - releaseElement(getParticipantPublicId(), el); - } - releaseElement(getParticipantPublicId(), publisher.getEndpoint()); - this.streaming = false; - this.session.deregisterPublisher(); - - endpointConfig.getCdr().stopPublisher(this.getParticipantPublicId(), publisher.getStreamId(), reason); - publisher = null; - } else { log.warn("PARTICIPANT {}: Trying to release publisher endpoint but is null", getParticipantPublicId()); } } - private void releaseSubscriberEndpoint(KurentoParticipant senderKurentoParticipant, SubscriberEndpoint subscriber, - EndReason reason, boolean silent) { - final String senderName = senderKurentoParticipant.getParticipantPublicId(); + private void releasePublisherEndpointAux(EndReason reason, long kmsDisconnectionTime) { + // Remove streamId from publisher's map + this.session.publishedStreamIds.remove(this.getPublisherStreamId()); + + if (this.openviduConfig.isRecordingModuleEnabled() + && this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) { + this.recordingManager.stopOneIndividualStreamRecording(session, this.getPublisherStreamId(), + kmsDisconnectionTime); + } + + publisher.unregisterErrorListeners(); + publisher.cancelStatsLoop.set(true); + + for (MediaElement el : publisher.getMediaElements()) { + releaseElement(getParticipantPublicId(), el); + } + releaseElement(getParticipantPublicId(), publisher.getEndpoint()); + this.streaming = false; + this.session.deregisterPublisher(); + + endpointConfig.getCdr().stopPublisher(this.getParticipantPublicId(), publisher.getStreamId(), reason); + publisher = null; + } + + private void releaseSubscriberEndpoint(String senderName, KurentoParticipant publisherParticipant, + SubscriberEndpoint subscriber, EndReason reason, boolean silent) { + if (subscriber != null) { subscriber.unregisterErrorListeners(); subscriber.cancelStatsLoop.set(true); - releaseElement(senderName, subscriber.getEndpoint()); + if (subscriber.getEndpoint() != null) { + releaseElement(senderName, subscriber.getEndpoint()); + } if (!silent) { // Stop PlayerEndpoint of IP CAM if last subscriber disconnected - final PublisherEndpoint senderPublisher = senderKurentoParticipant.publisher; - if (senderPublisher != null) { + if (publisherParticipant != null && publisherParticipant.publisher != null) { + final PublisherEndpoint senderPublisher = publisherParticipant.publisher; // If no PublisherEndpoint, then it means that the publisher already closed it final KurentoMediaOptions options = (KurentoMediaOptions) senderPublisher.getMediaOptions(); - if (options.onlyPlayWithSubscribers != null && options.onlyPlayWithSubscribers) { + if (options != null && options.onlyPlayWithSubscribers != null && options.onlyPlayWithSubscribers) { synchronized (senderPublisher) { senderPublisher.numberOfSubscribers--; if (senderPublisher.isPlayerEndpoint() && senderPublisher.numberOfSubscribers == 0) { diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSession.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSession.java index 22845b1b..e0ad8cb4 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSession.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSession.java @@ -89,15 +89,6 @@ public class KurentoSession extends Session { public void newPublisher(Participant participant) { registerPublisher(); - - // pre-load endpoints to recv video from the new publisher - for (Participant p : participants.values()) { - if (participant.equals(p)) { - continue; - } - ((KurentoParticipant) p).getNewOrExistingSubscriber(participant.getParticipantPublicId()); - } - log.debug("SESSION {}: Virtually subscribed other participants {} to new publisher {}", sessionId, participants.values(), participant.getParticipantPublicId()); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/PublisherEndpoint.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/PublisherEndpoint.java index 1dd07dc7..d2b0d748 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/PublisherEndpoint.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/PublisherEndpoint.java @@ -26,6 +26,8 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.kurento.client.Continuation; import org.kurento.client.GenericMediaElement; @@ -76,6 +78,16 @@ public class PublisherEndpoint extends MediaEndpoint { public int numberOfSubscribers = 0; + /** + * This lock protects the following method with read lock: + * KurentoParticipant#receiveMediaFrom. It uses tryLock, immediately failing if + * written locked + * + * Lock is written-locked upon KurentoParticipant#releasePublisherEndpoint and + * KurentoParticipant#cancelReceivingMedia + */ + public ReadWriteLock closingLock = new ReentrantReadWriteLock(); + public PublisherEndpoint(EndpointType endpointType, KurentoParticipant owner, String endpointName, MediaPipeline pipeline, OpenviduConfig openviduConfig, PassThrough passThru) { super(endpointType, owner, endpointName, pipeline, openviduConfig, log); diff --git a/openvidu-server/src/test/java/io/openvidu/server/config/DotenvTest.java b/openvidu-server/src/test/java/io/openvidu/server/config/DotenvTest.java new file mode 100644 index 00000000..fc7a9f87 --- /dev/null +++ b/openvidu-server/src/test/java/io/openvidu/server/config/DotenvTest.java @@ -0,0 +1,85 @@ +package io.openvidu.server.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import org.junit.jupiter.api.Test; + +import io.openvidu.server.config.Dotenv.DotenvFormatException; + +class DotenvTest { + + @Test + void loadTest() throws IOException, DotenvFormatException { + + // Given + Path envFile = createDotenvTestFile(); + + Dotenv dotenv = new Dotenv(); + + // When + dotenv.read(envFile); + + // Then + assertEquals("value1", dotenv.get("PROPERTY_ONE")); + assertEquals("value2", dotenv.get("PROPERTY_TWO")); + } + + @Test + void writeSameFileTest() throws IOException, DotenvFormatException { + + // Given + Path envFile = createDotenvTestFile(); + String originalEnvContent = new String(Files.readAllBytes(envFile)); + + Dotenv dotenv = new Dotenv(); + + // When + dotenv.read(envFile); + + Files.delete(envFile); + + dotenv.write(); + String recreatedEnvContent = new String(Files.readAllBytes(envFile)); + + // Then + assertEquals(originalEnvContent, recreatedEnvContent); + } + + @Test + void writeModifiedFileTest() throws IOException, DotenvFormatException { + + // Given + Path envFile = createDotenvTestFile(); + + Dotenv dotenv = new Dotenv(); + + // When + dotenv.read(envFile); + + dotenv.set("PROPERTY_ONE", "value_one"); + dotenv.set("PROPERTY_TWO", "value_two"); + dotenv.set("PROPERTY_THREE", "value_three"); + + dotenv.write(); + + Dotenv updDotenv = new Dotenv(); + updDotenv.read(envFile); + + // Then + assertEquals("value_one", updDotenv.get("PROPERTY_ONE")); + assertEquals("value_two", updDotenv.get("PROPERTY_TWO")); + assertEquals("value_three", updDotenv.get("PROPERTY_THREE")); + } + + private Path createDotenvTestFile() throws IOException { + Path envFile = Files.createTempFile("env", ".tmp"); + Files.copy(getClass().getResourceAsStream("/.env"), envFile, StandardCopyOption.REPLACE_EXISTING); + return envFile; + } + +} diff --git a/openvidu-server/src/test/resources/.env b/openvidu-server/src/test/resources/.env new file mode 100644 index 00000000..9562c7a9 --- /dev/null +++ b/openvidu-server/src/test/resources/.env @@ -0,0 +1,5 @@ +# Comment 1 +PROPERTY_ONE=value1 + +# Comment 2 +PROPERTY_TWO=value2