diff --git a/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java b/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java index c18fa9b9..32dc278b 100644 --- a/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java +++ b/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,19 +24,19 @@ import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.action.AliasActionType; import io.github.dsheirer.alias.action.RecurringAction; import io.github.dsheirer.audio.AudioFormats; -import io.github.dsheirer.gui.preference.playback.ToneFrequency; -import io.github.dsheirer.gui.preference.playback.ToneUtil; -import io.github.dsheirer.gui.preference.playback.ToneVolume; +import io.github.dsheirer.gui.preference.audio.playback.ToneFrequency; +import io.github.dsheirer.gui.preference.audio.playback.ToneUtil; +import io.github.dsheirer.gui.preference.audio.playback.ToneVolume; import io.github.dsheirer.message.IMessage; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.DataLine; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.ShortBuffer; public class BeepAction extends RecurringAction { diff --git a/src/main/java/io/github/dsheirer/audio/AudioManager.java b/src/main/java/io/github/dsheirer/audio/AudioManager.java index 9a55c949..0054c2b5 100644 --- a/src/main/java/io/github/dsheirer/audio/AudioManager.java +++ b/src/main/java/io/github/dsheirer/audio/AudioManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,13 +27,20 @@ import io.github.dsheirer.audio.call.CallRepository; import io.github.dsheirer.audio.call.ICallEventListener; import io.github.dsheirer.audio.playback.AudioPlaybackManager; import io.github.dsheirer.audio.playbackfx.AudioPlaybackController; +import io.github.dsheirer.log.LoggingSuppressor; import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.retention.AgeUnits; +import io.github.dsheirer.preference.retention.RetentionPolicy; +import io.github.dsheirer.preference.retention.RetentionPreference; import io.github.dsheirer.record.AudioRecordingManager; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.ThreadPool; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.annotation.Resource; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -44,9 +51,13 @@ import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; /** @@ -56,6 +67,8 @@ import org.springframework.stereotype.Component; public class AudioManager implements Listener { private static final Logger LOGGER = LoggerFactory.getLogger(AudioManager.class); + private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LOGGER); + private static final long GIGABYTES = 1024l * 1024l * 1024l; @Resource private AudioPlaybackManager mAudioPlaybackManager; @@ -71,8 +84,10 @@ public class AudioManager implements Listener private UserPreferences mUserPreferences; private CallRepository mCallRepository; private List mCallListeners = new CopyOnWriteArrayList<>(); - private ActiveAudioSegmentProcessor mProcessor = new ActiveAudioSegmentProcessor(); - private ScheduledFuture mProcessorFuture; + private ActiveAudioSegmentProcessor mAudioSegmentProcessor = new ActiveAudioSegmentProcessor(); + private CallAgeOffProcessor mCallAgeOffProcessor = new CallAgeOffProcessor(); + private ScheduledFuture mAudioSegmentProcessorFuture; + private ScheduledFuture mCallRetentionProcessorFuture; /** * Constructs an instance @@ -86,7 +101,12 @@ public class AudioManager implements Listener @PostConstruct public void postConstruct() { - mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mProcessor, 100, 500, TimeUnit.MILLISECONDS); + LOGGER.info("Post constructing ......"); + mAudioSegmentProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mAudioSegmentProcessor, 100, + 500, TimeUnit.MILLISECONDS); + //Delay first run until 1-minute after startup. + mCallRetentionProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(mCallAgeOffProcessor, + 30, 30, TimeUnit.SECONDS); //Register call event listeners add(mAudioRecordingManager); @@ -97,13 +117,20 @@ public class AudioManager implements Listener @PreDestroy public void preDestroy() { - if(mProcessorFuture != null) - { - mProcessorFuture.cancel(true); - mProcessorFuture = null; - } LOGGER.info("AudioManager shutting down"); - mProcessor.shutdown(); + + if(mAudioSegmentProcessorFuture != null) + { + mAudioSegmentProcessorFuture.cancel(true); + mAudioSegmentProcessorFuture = null; + } + mAudioSegmentProcessor.shutdown(); + + if(mCallRetentionProcessorFuture != null) + { + mCallRetentionProcessorFuture.cancel(true); + mCallRetentionProcessorFuture = null; + } } /** @@ -183,8 +210,8 @@ public class AudioManager implements Listener //Let the duplicate call detector process the segment first to detect duplicates. audioSegment.addLease(mDuplicateCallDetector.getClass().toString()); mDuplicateCallDetector.receive(audioSegment); - audioSegment.addLease(mProcessor.getClass().toString()); - mProcessor.add(audioSegment); + audioSegment.addLease(mAudioSegmentProcessor.getClass().toString()); + mAudioSegmentProcessor.add(audioSegment); //TODO: remove for cleanup // audioSegment.addLease(mAudioPlaybackManager.getClass().toString()); @@ -314,4 +341,163 @@ public class AudioManager implements Listener } } } + + /** + * Call event retention and age-off processor. + */ + public class CallAgeOffProcessor implements Runnable + { + @Override + public void run() + { + RetentionPreference retentionPreference = mUserPreferences.getRetentionPreference(); + RetentionPolicy policy = retentionPreference.getRetentionPolicy(); + + try + { + if(policy.isAgePolicy()) + { + AgeUnits ageUnits = retentionPreference.getAgeUnits(); + int ageValue = retentionPreference.getAgeValue(); + long threshold = System.currentTimeMillis() - ageUnits.getDuration(ageValue); + PageRequest requestPage1 = PageRequest.of(0, 100); + long start = 0; + Page page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, threshold, requestPage1); + + while(page.getTotalElements() > 0) + { + List calls = page.get().collect(Collectors.toList()); + + for(Call call: calls) + { + //Keep adjusting the start time to 1 millisecond more than the current call so that + //if there's an error deleting any given call, we don't get stuck processing it every time + start = call.getEventTime() + 1; + + //Only evaluate calls that are complete + if(call.isComplete()) + { + boolean deleteFailed = false; + File file = new File(call.getFile()); + if(file.exists()) + { + try + { + File parentDirectory = file.getParentFile(); + Files.delete(file.toPath()); + + //Delete parent directory if it is now empty + if(parentDirectory.exists() && parentDirectory.isDirectory()) + { + if(!Files.list(parentDirectory.toPath()).findFirst().isPresent()) + { + Files.delete(parentDirectory.toPath()); + } + } + } + catch(Exception io) + { + LOGGING_SUPPRESSOR.error("Call Event Delete", 5, + "Error deleting call event recording: " + call.getFile() + + " - " + io.getLocalizedMessage()); + deleteFailed = true; + } + } + + if(!deleteFailed) + { + mCallRepository.delete(call); + broadcastDeleted(call); + } + } + } + + page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, threshold, requestPage1); + } + } + + if(policy.isSizePolicy()) + { + Path callsDirectory = mUserPreferences.getDirectoryPreference().getDirectoryCalls(); + + if(Files.exists(callsDirectory)) + { + long currentSize = FileUtils.sizeOfDirectory(callsDirectory.toFile()); + long objectiveSize = retentionPreference.getObjectiveDirectorySize(); + + if(currentSize >= objectiveSize) + { + long start = 0; + long now = System.currentTimeMillis(); + PageRequest pageable = PageRequest.of(0, 100); + Page page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, now, pageable); + + while(currentSize >= objectiveSize && page.getTotalElements() > 0) + { + List calls = page.get().collect(Collectors.toList()); + + for(Call call: calls) + { + //Keep adjusting the start time to 1 millisecond more than the current call so that + //if there's an error deleting any given call, we don't get stuck processing it every time + start = call.getEventTime() + 1; + + if(currentSize < objectiveSize) + { + start = now - 1; + break; + } + + //Only evaluate calls that are complete + if(call.isComplete()) + { + boolean deleteFailed = false; + File file = new File(call.getFile()); + if(file.exists()) + { + try + { + File parentDirectory = file.getParentFile(); + long fileSize = FileUtils.sizeOf(file); + Files.delete(file.toPath()); + currentSize -= fileSize; + + //Delete parent directory if it is now empty + if(parentDirectory.exists() && parentDirectory.isDirectory()) + { + if(!Files.list(parentDirectory.toPath()).findFirst().isPresent()) + { + Files.delete(parentDirectory.toPath()); + } + } + } + catch(Exception io) + { + LOGGING_SUPPRESSOR.error("Call Event Delete", 5, + "Error deleting call event recording: " + call.getFile() + + " - " + io.getLocalizedMessage()); + deleteFailed = true; + } + } + + if(!deleteFailed) + { + mCallRepository.delete(call); + broadcastDeleted(call); + } + } + } + + page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, now, pageable); + } + } + } + } + } + catch(Throwable t) + { + LOGGER.error("Unexpected error while deleting call events using retention policy: " + policy, t); + } + } + } } diff --git a/src/main/java/io/github/dsheirer/audio/call/ActiveAudioSegment.java b/src/main/java/io/github/dsheirer/audio/call/ActiveAudioSegment.java index dcc02734..2b2b15c2 100644 --- a/src/main/java/io/github/dsheirer/audio/call/ActiveAudioSegment.java +++ b/src/main/java/io/github/dsheirer/audio/call/ActiveAudioSegment.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -59,7 +59,6 @@ public class ActiveAudioSegment private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LOGGER); private static final SimpleDateFormat DIRECTORY_SDF = new SimpleDateFormat("yyyyMMdd"); private static final SimpleDateFormat FILE_NAME_SDF = new SimpleDateFormat("yyyyMMdd_HHmmss"); - private static final String CALLS_DIRECTORY_FOLDER = "calls"; private static int sFileNameCounter = 1; private AudioSegment mAudioSegment; @@ -152,8 +151,8 @@ public class ActiveAudioSegment { Date now = new Date(); String dailyFolder = DIRECTORY_SDF.format(now); - Path recordingDirectory = mUserPreferences.getDirectoryPreference().getDirectoryRecording() - .resolve(CALLS_DIRECTORY_FOLDER).resolve(dailyFolder); + Path recordingDirectory = mUserPreferences.getDirectoryPreference().getDirectoryCalls() + .resolve(dailyFolder); if(!Files.exists(recordingDirectory)) { diff --git a/src/main/java/io/github/dsheirer/audio/call/Call.java b/src/main/java/io/github/dsheirer/audio/call/Call.java index b9becebc..4be7d9ab 100644 --- a/src/main/java/io/github/dsheirer/audio/call/Call.java +++ b/src/main/java/io/github/dsheirer/audio/call/Call.java @@ -63,41 +63,41 @@ public class Call @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_generator") @SequenceGenerator(name = "id_generator", allocationSize = 1) @Column(name=COLUMN_ID) - private long mId; + private long id; @Column(name=COLUMN_CALL_TYPE) - private String mCallType; + private String callType; @Column(name=COLUMN_CHANNEL) - private String mChannel; + private String channel; @Column(name=COLUMN_DUPLICATE) - private boolean mDuplicate; + private boolean duplicate; @Column(name=COLUMN_DURATION) - private double mDuration; + private double duration; @Column(name=COLUMN_EVENT_TIME) - private long mEventTime; + private long eventTime; @Column(name=COLUMN_FILE) - private String mFile; + private String file; @Column(name=COLUMN_FREQUENCY) - private double mFrequency; + private double frequency; @Column(name=COLUMN_FROM_ALIAS) - private String mFromAlias; + private String fromAlias; @Column(name=COLUMN_FROM_ID) - private String mFromId; + private String fromId; @Column(name=COLUMN_MONITOR) - private int mMonitor; + private int monitor; @Column(name=COLUMN_PROTOCOL) - private String mProtocol; + private String protocol; @Column(name=COLUMN_RECORD) - private boolean mRecord; + private boolean record; @Column(name=COLUMN_SITE) - private String mSite; + private String site; @Column(name=COLUMN_STREAM) - private boolean mStream; + private boolean stream; @Column(name=COLUMN_SYSTEM) - private String mSystem; + private String system; @Column(name=COLUMN_TO_ALIAS) - private String mToAlias; + private String toAlias; @Column(name=COLUMN_TO_ID) - private String mToId; + private String toId; @Transient private boolean mComplete = true; @@ -119,7 +119,7 @@ public class Call */ public long getId() { - return mId; + return id; } /** @@ -128,7 +128,7 @@ public class Call */ public long getEventTime() { - return mEventTime; + return eventTime; } /** @@ -137,7 +137,7 @@ public class Call */ public void setEventTime(long eventTime) { - mEventTime = eventTime; + this.eventTime = eventTime; } /** @@ -146,7 +146,7 @@ public class Call */ public String getFromId() { - return mFromId; + return fromId; } /** @@ -155,7 +155,7 @@ public class Call */ public void setFromId(String fromId) { - mFromId = fromId; + this.fromId = fromId; updateTimestamp(); } @@ -165,7 +165,7 @@ public class Call */ public String getFromAlias() { - return mFromAlias; + return fromAlias; } /** @@ -174,7 +174,7 @@ public class Call */ public void setFromAlias(String fromAlias) { - mFromAlias = fromAlias; + this.fromAlias = fromAlias; updateTimestamp(); } @@ -184,7 +184,7 @@ public class Call */ public String getToId() { - return mToId; + return toId; } /** @@ -193,7 +193,7 @@ public class Call */ public void setToId(String toId) { - mToId = toId; + this.toId = toId; updateTimestamp(); } @@ -203,7 +203,7 @@ public class Call */ public String getToAlias() { - return mToAlias; + return toAlias; } /** @@ -212,7 +212,7 @@ public class Call */ public void setToAlias(String toAlias) { - mToAlias = toAlias; + this.toAlias = toAlias; updateTimestamp(); } @@ -222,7 +222,7 @@ public class Call */ public String getSystem() { - return mSystem; + return system; } /** @@ -231,7 +231,7 @@ public class Call */ public void setSystem(String system) { - mSystem = system; + this.system = system; } /** @@ -240,7 +240,7 @@ public class Call */ public String getSite() { - return mSite; + return site; } /** @@ -249,7 +249,7 @@ public class Call */ public void setSite(String site) { - mSite = site; + this.site = site; } /** @@ -258,7 +258,7 @@ public class Call */ public String getChannel() { - return mChannel; + return channel; } /** @@ -267,7 +267,7 @@ public class Call */ public void setChannel(String channel) { - mChannel = channel; + this.channel = channel; } /** @@ -276,7 +276,7 @@ public class Call */ public double getFrequency() { - return mFrequency; + return frequency; } /** @@ -285,7 +285,7 @@ public class Call */ public void setFrequency(double frequency) { - mFrequency = frequency; + this.frequency = frequency; } /** @@ -294,7 +294,7 @@ public class Call */ public String getFile() { - return mFile; + return file; } /** @@ -303,7 +303,7 @@ public class Call */ public void setFile(String file) { - mFile = file; + this.file = file; } /** @@ -312,7 +312,7 @@ public class Call */ public double getDuration() { - return mDuration; + return duration; } /** @@ -321,7 +321,7 @@ public class Call */ public void setDuration(double duration) { - mDuration = duration; + this.duration = duration; updateTimestamp(); } @@ -331,7 +331,7 @@ public class Call */ public String getProtocol() { - return mProtocol; + return protocol; } /** @@ -340,7 +340,7 @@ public class Call */ public void setProtocol(String protocol) { - mProtocol = protocol; + this.protocol = protocol; } /** @@ -368,7 +368,7 @@ public class Call */ public String getCallType() { - return mCallType; + return callType; } /** @@ -377,7 +377,7 @@ public class Call */ public void setCallType(String callType) { - mCallType = callType; + this.callType = callType; } /** @@ -386,7 +386,7 @@ public class Call */ public boolean isDuplicate() { - return mDuplicate; + return duplicate; } /** @@ -395,7 +395,7 @@ public class Call */ public void setDuplicate(boolean duplicate) { - mDuplicate = duplicate; + this.duplicate = duplicate; } /** @@ -404,7 +404,7 @@ public class Call */ public int getMonitor() { - return mMonitor; + return monitor; } /** @@ -413,7 +413,7 @@ public class Call */ public void setMonitor(int monitor) { - mMonitor = monitor; + this.monitor = monitor; } /** @@ -422,7 +422,7 @@ public class Call */ public boolean isRecord() { - return mRecord; + return record; } /** @@ -431,7 +431,7 @@ public class Call */ public void setRecord(boolean record) { - mRecord = record; + this.record = record; } /** @@ -440,7 +440,7 @@ public class Call */ public boolean isStream() { - return mStream; + return stream; } /** @@ -449,7 +449,7 @@ public class Call */ public void setStream(boolean stream) { - mStream = stream; + this.stream = stream; } /** @@ -485,5 +485,11 @@ public class Call { return mPlaybackChannel; } + + @Override + public boolean equals(Object obj) + { + return obj instanceof Call c && getId() == c.getId(); + } } diff --git a/src/main/java/io/github/dsheirer/audio/call/CallRepository.java b/src/main/java/io/github/dsheirer/audio/call/CallRepository.java index d442e4ac..175665f1 100644 --- a/src/main/java/io/github/dsheirer/audio/call/CallRepository.java +++ b/src/main/java/io/github/dsheirer/audio/call/CallRepository.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,27 +29,35 @@ import org.springframework.data.jpa.repository.Query; */ public interface CallRepository extends JpaRepository { - @Query("SELECT t FROM Call t WHERE t.mToId like ?1 OR t.mToAlias like ?1 OR t.mFromId like ?1 OR t.mFromAlias like ?1") + @Query("SELECT t FROM Call t WHERE t.toId like ?1 OR t.toAlias like ?1 OR t.fromId like ?1 OR t.fromAlias like ?1") Page findByAnyIdWithPagination(String value, Pageable pageable); - @Query("SELECT t FROM Call t WHERE t.mToId like ?1 OR t.mToAlias like ?1") + @Query("SELECT t FROM Call t WHERE t.toId like ?1 OR t.toAlias like ?1") Page findByToWithPagination(String value, Pageable pageable); - @Query("SELECT t FROM Call t WHERE t.mFromId like ?1 OR t.mFromAlias like ?1") + @Query("SELECT t FROM Call t WHERE t.fromId like ?1 OR t.fromAlias like ?1") Page findByFromWithPagination(String value, Pageable pageable); - @Query("SELECT t FROM Call t WHERE t.mCallType like ?1") + @Query("SELECT t FROM Call t WHERE t.callType like ?1") Page findByCallTypeWithPagination(String value, Pageable pageable); - @Query("SELECT t FROM Call t WHERE t.mChannel like ?1") + @Query("SELECT t FROM Call t WHERE t.channel like ?1") Page findByChannelWithPagination(String value, Pageable pageable); - @Query("SELECT t FROM Call t WHERE t.mProtocol like ?1") + @Query("SELECT t FROM Call t WHERE t.protocol like ?1") Page findByProtocolWithPagination(String value, Pageable pageable); - @Query("SELECT t FROM Call t WHERE t.mSite like ?1") + @Query("SELECT t FROM Call t WHERE t.site like ?1") Page findBySiteWithPagination(String value, Pageable pageable); - @Query("SELECT t FROM Call t WHERE t.mSystem like ?1") + @Query("SELECT t FROM Call t WHERE t.system like ?1") Page findBySystemWithPagination(String value, Pageable pageable); + + /** + * Find all calls between the timestamp range arguments. + * @param start timestamp + * @param end timestamp + * @return pageable set of calls. + */ + Page findCallsByEventTimeBetweenOrderByEventTime(long start, long end, Pageable pageable); } diff --git a/src/main/java/io/github/dsheirer/audio/call/CallView.java b/src/main/java/io/github/dsheirer/audio/call/CallView.java index 23f58044..f289df18 100644 --- a/src/main/java/io/github/dsheirer/audio/call/CallView.java +++ b/src/main/java/io/github/dsheirer/audio/call/CallView.java @@ -78,7 +78,7 @@ import org.springframework.data.domain.Sort; public class CallView extends VBox implements IPageRequestListener { private static final Logger LOGGER = LoggerFactory.getLogger(CallView.class); - private static final Sort DEFAULT_SORT = Sort.by("mEventTime").descending(); + private static final Sort DEFAULT_SORT = Sort.by("eventTime").descending(); @Resource private AudioManager mAudioManager; @@ -613,7 +613,7 @@ public class CallView extends VBox implements IPageRequestListener TableColumn playbackColumn = new TableColumn(); playbackColumn.setSortable(false); - playbackColumn.setId("mPlayback"); + playbackColumn.setId("playback"); playbackColumn.sortTypeProperty().addListener(mSortOrderListener); IconNode iconNode = new IconNode(Elusive.VOLUME_UP); iconNode.setIconSize(16); @@ -623,7 +623,7 @@ public class CallView extends VBox implements IPageRequestListener playbackColumn.setPrefWidth(40); TableColumn eventTimeColumn = new TableColumn(); - eventTimeColumn.setId("mEventTime"); + eventTimeColumn.setId("eventTime"); eventTimeColumn.sortTypeProperty().addListener(mSortOrderListener); eventTimeColumn.setText("Date/Time"); eventTimeColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_EVENT_TIME)); @@ -631,63 +631,63 @@ public class CallView extends VBox implements IPageRequestListener eventTimeColumn.setPrefWidth(160); TableColumn durationColumn = new TableColumn(); - durationColumn.setId("mDuration"); + durationColumn.setId("duration"); durationColumn.sortTypeProperty().addListener(mSortOrderListener); durationColumn.setText("Duration"); durationColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_DURATION)); durationColumn.setPrefWidth(70); TableColumn callTypeColumn = new TableColumn(); - callTypeColumn.setId("mCallType"); + callTypeColumn.setId("callType"); callTypeColumn.sortTypeProperty().addListener(mSortOrderListener); callTypeColumn.setText("Type"); callTypeColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_CALL_TYPE)); callTypeColumn.setPrefWidth(85); TableColumn toIdColumn = new TableColumn(); - toIdColumn.setId("mToId"); + toIdColumn.setId("toId"); toIdColumn.sortTypeProperty().addListener(mSortOrderListener); toIdColumn.setText("To"); toIdColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_TO_ID)); toIdColumn.setPrefWidth(80); TableColumn toAliasColumn = new TableColumn(); - toAliasColumn.setId("mToAlias"); + toAliasColumn.setId("toAlias"); toAliasColumn.sortTypeProperty().addListener(mSortOrderListener); toAliasColumn.setText("To Alias"); toAliasColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_TO_ALIAS)); toAliasColumn.setPrefWidth(100); TableColumn fromIdColumn = new TableColumn(); - fromIdColumn.setId("mFromId"); + fromIdColumn.setId("fromId"); fromIdColumn.sortTypeProperty().addListener(mSortOrderListener); fromIdColumn.setText("From"); fromIdColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_FROM_ID)); fromIdColumn.setPrefWidth(70); TableColumn fromAliasColumn = new TableColumn(); - fromAliasColumn.setId("mFromAlias"); + fromAliasColumn.setId("fromAlias"); fromAliasColumn.sortTypeProperty().addListener(mSortOrderListener); fromAliasColumn.setText("From Alias"); fromAliasColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_FROM_ALIAS)); fromAliasColumn.setPrefWidth(100); TableColumn protocolColumn = new TableColumn(); - protocolColumn.setId("mProtocol"); + protocolColumn.setId("protocol"); protocolColumn.sortTypeProperty().addListener(mSortOrderListener); protocolColumn.setText("Protocol"); protocolColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_PROTOCOL)); protocolColumn.setPrefWidth(90); TableColumn frequencyColumn = new TableColumn(); - frequencyColumn.setId("mFrequency"); + frequencyColumn.setId("frequency"); frequencyColumn.sortTypeProperty().addListener(mSortOrderListener); frequencyColumn.setText("Frequency"); frequencyColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_FREQUENCY)); frequencyColumn.setPrefWidth(80); TableColumn systemColumn = new TableColumn(); - systemColumn.setId("mSystem"); + systemColumn.setId("system"); systemColumn.sortTypeProperty().addListener(mSortOrderListener); systemColumn.setText("System"); systemColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_SYSTEM)); @@ -703,7 +703,7 @@ public class CallView extends VBox implements IPageRequestListener TableColumn channelColumn = new TableColumn(); channelColumn.setVisible(false); - channelColumn.setId("mChannel"); + channelColumn.setId("channel"); channelColumn.sortTypeProperty().addListener(mSortOrderListener); channelColumn.setText("Channel"); channelColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_CHANNEL)); @@ -711,7 +711,7 @@ public class CallView extends VBox implements IPageRequestListener TableColumn duplicateColumn = new TableColumn(); duplicateColumn.setVisible(false); - duplicateColumn.setId("mDuplicate"); + duplicateColumn.setId("duplicate"); duplicateColumn.sortTypeProperty().addListener(mSortOrderListener); duplicateColumn.setText("Duplicate"); duplicateColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_DUPLICATE)); @@ -719,7 +719,7 @@ public class CallView extends VBox implements IPageRequestListener TableColumn recordColumn = new TableColumn(); recordColumn.setVisible(false); - recordColumn.setId("mRecord"); + recordColumn.setId("record"); recordColumn.sortTypeProperty().addListener(mSortOrderListener); recordColumn.setText("Record"); recordColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_RECORD)); @@ -727,7 +727,7 @@ public class CallView extends VBox implements IPageRequestListener TableColumn streamColumn = new TableColumn(); streamColumn.setVisible(false); - streamColumn.setId("mStream"); + streamColumn.setId("stream"); streamColumn.sortTypeProperty().addListener(mSortOrderListener); streamColumn.setText("Stream"); streamColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_STREAM)); @@ -735,7 +735,7 @@ public class CallView extends VBox implements IPageRequestListener TableColumn monitorColumn = new TableColumn(); monitorColumn.setVisible(false); - monitorColumn.setId("mMonitor"); + monitorColumn.setId("monitor"); monitorColumn.sortTypeProperty().addListener(mSortOrderListener); monitorColumn.setText("Monitor"); monitorColumn.setCellValueFactory(new PropertyValueFactory(Call.COLUMN_MONITOR)); @@ -752,6 +752,11 @@ public class CallView extends VBox implements IPageRequestListener { mAudioPlaybackController.replay(MixerChannel.LEFT, selectedCall); } + else if(oldValue != null && selectedCall == null) + { + //We were in replay but now we don't have a selected call + mAudioPlaybackController.auto(MixerChannel.LEFT, null, null); + } AudioPlaybackChannelController left = mAudioPlaybackController.getChannelController(MixerChannel.LEFT); AudioPlaybackChannelController right = mAudioPlaybackController.getChannelController(MixerChannel.RIGHT); @@ -911,6 +916,8 @@ public class CallView extends VBox implements IPageRequestListener { mCalls.remove(call); } + + mCallCount.set(mCallCount.get() - 1); }); } } diff --git a/src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelController.java b/src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelController.java index dadfa706..2e5e8535 100644 --- a/src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelController.java +++ b/src/main/java/io/github/dsheirer/audio/playbackfx/AudioPlaybackChannelController.java @@ -32,6 +32,7 @@ import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.scene.media.Media; +import javafx.scene.media.MediaException; import javafx.scene.media.MediaPlayer; /** @@ -41,6 +42,7 @@ public class AudioPlaybackChannelController implements ChangeListener mMediaPlayer = new SimpleObjectProperty<>(); private StringProperty mFrequency = new SimpleStringProperty(); @@ -233,18 +235,31 @@ public class AudioPlaybackChannelController implements ChangeListener audioItem = new TreeItem<>("Audio"); - audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_CALL_MANAGEMENT)); - audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_MP3)); + audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_CALL_RETENTION)); + audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_DUPLICATE_MANAGEMENT)); audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_PLAYBACK)); audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_RECORD)); treeRoot.getChildren().add(audioItem); diff --git a/src/main/java/io/github/dsheirer/gui/preference/call/CallManagementPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/audio/call/CallManagementPreferenceEditor.java similarity index 98% rename from src/main/java/io/github/dsheirer/gui/preference/call/CallManagementPreferenceEditor.java rename to src/main/java/io/github/dsheirer/gui/preference/audio/call/CallManagementPreferenceEditor.java index e2c5093d..f226db58 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/call/CallManagementPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/audio/call/CallManagementPreferenceEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ * **************************************************************************** */ -package io.github.dsheirer.gui.preference.call; +package io.github.dsheirer.gui.preference.audio.call; import io.github.dsheirer.audio.broadcast.PatchGroupStreamingOption; import io.github.dsheirer.gui.preference.PreferenceEditor; @@ -71,7 +71,7 @@ public class CallManagementPreferenceEditor extends PreferenceEditor @Override public PreferenceEditorType getPreferenceEditorType() { - return PreferenceEditorType.AUDIO_CALL_MANAGEMENT; + return PreferenceEditorType.AUDIO_DUPLICATE_MANAGEMENT; } private GridPane getEditorPane() diff --git a/src/main/java/io/github/dsheirer/gui/preference/playback/PlaybackPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/audio/playback/PlaybackPreferenceEditor.java similarity index 99% rename from src/main/java/io/github/dsheirer/gui/preference/playback/PlaybackPreferenceEditor.java rename to src/main/java/io/github/dsheirer/gui/preference/audio/playback/PlaybackPreferenceEditor.java index 49db26de..9f2bc7b8 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/playback/PlaybackPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/audio/playback/PlaybackPreferenceEditor.java @@ -17,7 +17,7 @@ * **************************************************************************** */ -package io.github.dsheirer.gui.preference.playback; +package io.github.dsheirer.gui.preference.audio.playback; import io.github.dsheirer.audio.AudioFormats; import io.github.dsheirer.audio.playbackfx.PlaybackView; diff --git a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneFrequency.java b/src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneFrequency.java similarity index 94% rename from src/main/java/io/github/dsheirer/gui/preference/playback/ToneFrequency.java rename to src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneFrequency.java index e2dbb3f7..26dd3d07 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneFrequency.java +++ b/src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneFrequency.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ * **************************************************************************** */ -package io.github.dsheirer.gui.preference.playback; +package io.github.dsheirer.gui.preference.audio.playback; public enum ToneFrequency { diff --git a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java b/src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneUtil.java similarity index 95% rename from src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java rename to src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneUtil.java index e64bad3b..9ac23b3a 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneUtil.java +++ b/src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneUtil.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ * **************************************************************************** */ -package io.github.dsheirer.gui.preference.playback; +package io.github.dsheirer.gui.preference.audio.playback; import io.github.dsheirer.dsp.oscillator.IRealOscillator; import io.github.dsheirer.dsp.oscillator.ScalarRealOscillator; diff --git a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneVolume.java b/src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneVolume.java similarity index 93% rename from src/main/java/io/github/dsheirer/gui/preference/playback/ToneVolume.java rename to src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneVolume.java index 9337604c..0ba7b306 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/playback/ToneVolume.java +++ b/src/main/java/io/github/dsheirer/gui/preference/audio/playback/ToneVolume.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ * **************************************************************************** */ -package io.github.dsheirer.gui.preference.playback; +package io.github.dsheirer.gui.preference.audio.playback; public enum ToneVolume { diff --git a/src/main/java/io/github/dsheirer/gui/preference/mp3/MP3PreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/audio/record/RecordPreferenceEditor.java similarity index 75% rename from src/main/java/io/github/dsheirer/gui/preference/mp3/MP3PreferenceEditor.java rename to src/main/java/io/github/dsheirer/gui/preference/audio/record/RecordPreferenceEditor.java index 855397b5..64977a32 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/mp3/MP3PreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/audio/record/RecordPreferenceEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,20 +17,25 @@ * **************************************************************************** */ -package io.github.dsheirer.gui.preference.mp3; +package io.github.dsheirer.gui.preference.audio.record; import io.github.dsheirer.audio.convert.InputAudioFormat; import io.github.dsheirer.audio.convert.MP3Setting; import io.github.dsheirer.gui.preference.PreferenceEditor; import io.github.dsheirer.gui.preference.PreferenceEditorType; import io.github.dsheirer.preference.mp3.MP3Preference; +import io.github.dsheirer.preference.record.RecordPreference; +import io.github.dsheirer.record.RecordFormat; import jakarta.annotation.PostConstruct; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.scene.control.Alert; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; +import javafx.scene.control.Separator; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; @@ -39,21 +44,20 @@ import org.slf4j.LoggerFactory; /** - * Preference settings for MP3 + * Preference settings for recording */ -public class MP3PreferenceEditor extends PreferenceEditor +public class RecordPreferenceEditor extends PreferenceEditor { - private final static Logger mLog = LoggerFactory.getLogger(MP3PreferenceEditor.class); + private final static Logger mLog = LoggerFactory.getLogger(RecordPreferenceEditor.class); + private RecordPreference mRecordPreference; private MP3Preference mMP3Preference; private GridPane mEditorPane; + private ComboBox mRecordFormatComboBox; private ComboBox mMP3SettingComboBox; private ComboBox mAudioSampleRateComboBox; private CheckBox mNormalizeAudioCheckBox; - /** - * Constructs an instance - */ - public MP3PreferenceEditor() + public RecordPreferenceEditor() { } @@ -61,6 +65,7 @@ public class MP3PreferenceEditor extends PreferenceEditor public void postConstruct() { mMP3Preference = getUserPreferences().getMP3Preference(); + mRecordPreference = getUserPreferences().getRecordPreference(); HBox.setHgrow(getEditorPane(), Priority.ALWAYS); getChildren().add(getEditorPane()); } @@ -68,7 +73,7 @@ public class MP3PreferenceEditor extends PreferenceEditor @Override public PreferenceEditorType getPreferenceEditorType() { - return PreferenceEditorType.AUDIO_MP3; + return PreferenceEditorType.AUDIO_RECORD; } private GridPane getEditorPane() @@ -81,15 +86,28 @@ public class MP3PreferenceEditor extends PreferenceEditor mEditorPane.setVgap(10); int row = 0; + + Label label = new Label("Audio Recording Format:"); + mEditorPane.add(label, 0, row); + mEditorPane.add(getRecordFormatComboBox(), 1, row); + + row++; + + Separator separator = new Separator(); + GridPane.setHgrow(separator, Priority.ALWAYS); + GridPane.setConstraints(separator, 0, row, 2, 1); + mEditorPane.getChildren().add(separator); + + row++; + Label topLabel = new Label("MP3 Encoder Preferences"); mEditorPane.add(topLabel, 0, row++, 2, 1); - mEditorPane.add(getNormalizeAudioCheckBox(), 1, row++); - Label label = new Label("(LAME) Encoder Setting:"); - GridPane.setHalignment(label, HPos.RIGHT); - mEditorPane.add(label, 0, row); + Label lameLabel = new Label("(LAME) Encoder Setting:"); + GridPane.setHalignment(lameLabel, HPos.RIGHT); + mEditorPane.add(lameLabel, 0, row); mEditorPane.add(getMP3SettingComboBox(), 1, row++); Label sampleRateLabel = new Label("Input Audio Sample Rate:"); @@ -104,6 +122,26 @@ public class MP3PreferenceEditor extends PreferenceEditor return mEditorPane; } + private ComboBox getRecordFormatComboBox() + { + if(mRecordFormatComboBox == null) + { + mRecordFormatComboBox = new ComboBox<>(); + mRecordFormatComboBox.getItems().addAll(RecordFormat.values()); + mRecordFormatComboBox.getSelectionModel().select(mRecordPreference.getAudioRecordFormat()); + mRecordFormatComboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() + { + @Override + public void changed(ObservableValue observable, RecordFormat oldValue, RecordFormat newValue) + { + mRecordPreference.setAudioRecordFormat(newValue); + } + }); + } + + return mRecordFormatComboBox; + } + private ComboBox getMP3SettingComboBox() { if(mMP3SettingComboBox == null) diff --git a/src/main/java/io/github/dsheirer/gui/preference/audio/retention/RetentionPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/audio/retention/RetentionPreferenceEditor.java new file mode 100644 index 00000000..a45be405 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/preference/audio/retention/RetentionPreferenceEditor.java @@ -0,0 +1,277 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.gui.preference.audio.retention; + +import io.github.dsheirer.gui.control.IntegerTextField; +import io.github.dsheirer.gui.preference.PreferenceEditor; +import io.github.dsheirer.gui.preference.PreferenceEditorType; +import io.github.dsheirer.preference.retention.AgeUnits; +import io.github.dsheirer.preference.retention.RetentionPolicy; +import io.github.dsheirer.preference.retention.RetentionPreference; +import io.github.dsheirer.preference.retention.SizeUnits; +import jakarta.annotation.PostConstruct; +import javafx.collections.FXCollections; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Preference settings for audio/call retention + */ +public class RetentionPreferenceEditor extends PreferenceEditor +{ + private final static Logger mLog = LoggerFactory.getLogger(RetentionPreferenceEditor.class); + private RetentionPreference mRetentionPreference; + private GridPane mEditorPane; + private ComboBox mRetentionPolicyComboBox; + private ComboBox mAgeUnitsComboBox; + private ComboBox mSizeUnitsComboBox; + private IntegerTextField mAgeValueTextField; + private IntegerTextField mDirectorySizeTextField; + + /** + * Constructs an instance. + */ + public RetentionPreferenceEditor() + { + } + + @PostConstruct + public void postConstruct() + { + mRetentionPreference = getUserPreferences().getRetentionPreference(); + HBox.setHgrow(getEditorPane(), Priority.ALWAYS); + getChildren().add(getEditorPane()); + } + + @Override + public PreferenceEditorType getPreferenceEditorType() + { + return PreferenceEditorType.AUDIO_CALL_RETENTION; + } + + private GridPane getEditorPane() + { + if(mEditorPane == null) + { + mEditorPane = new GridPane(); + mEditorPane.setPadding(new Insets(10, 10, 10, 10)); + mEditorPane.setHgap(10); + mEditorPane.setVgap(10); + + int row = 0; + + Label label = new Label("Call event retention settings for automatic deletion of call " + + "events and related audio. Note: these settings do not apply to alias-based audio recordings."); + label.setWrapText(true); + mEditorPane.add(label, 0, row, 4, 1); + + Label autoDeleteLabel = new Label("Automatic Call Event Deletion"); + mEditorPane.add(autoDeleteLabel, 0, ++row, 4, 1); + + Label deleteByLabel = new Label("Delete By:"); + GridPane.setHalignment(deleteByLabel, HPos.RIGHT); + mEditorPane.add(deleteByLabel, 1, ++row); + mEditorPane.add(getRetentionPolicyComboBox(), 2, row); + + Label maxAgeLabel = new Label("Maximum Age:"); + GridPane.setHalignment(maxAgeLabel, HPos.RIGHT); + mEditorPane.add(maxAgeLabel, 1, ++row); + mEditorPane.add(getAgeValueTextField(), 2, row); + mEditorPane.add(getAgeUnitsComboBox(), 3, row); + + Label maxSizeLabel = new Label("Maximum Directory Size:"); + GridPane.setHalignment(maxSizeLabel, HPos.RIGHT); + mEditorPane.add(maxSizeLabel, 1, ++row); + mEditorPane.add(getDirectorySizeTextField(), 2, row); + mEditorPane.add(getSizeUnitsComboBox(), 3, row); + + updateControls(); + } + + return mEditorPane; + } + + /** + * Updates the enabled state of editor controls whenever the retention policy changes. + */ + private void updateControls() + { + RetentionPolicy policy = getRetentionPolicyComboBox().getValue(); + + switch(policy) + { + case AGE: + getAgeValueTextField().setDisable(false); + getAgeUnitsComboBox().setDisable(false); + getDirectorySizeTextField().setDisable(true); + break; + case SIZE: + getAgeValueTextField().setDisable(true); + getAgeUnitsComboBox().setDisable(true); + getDirectorySizeTextField().setDisable(false); + break; + default: + case AGE_AND_SIZE: + getAgeValueTextField().setDisable(false); + getAgeUnitsComboBox().setDisable(false); + getDirectorySizeTextField().setDisable(false); + break; + } + } + + public ComboBox getRetentionPolicyComboBox() + { + if(mRetentionPolicyComboBox == null) + { + mRetentionPolicyComboBox = new ComboBox<>(FXCollections.observableArrayList(RetentionPolicy.values())); + mRetentionPolicyComboBox.getSelectionModel().select(mRetentionPreference.getRetentionPolicy()); + mRetentionPolicyComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if(newValue != null) + { + mRetentionPreference.setRetentionPolicy(newValue); + } + + updateControls(); + }); + } + + return mRetentionPolicyComboBox; + } + + /** + * Age units combo box control. + */ + public ComboBox getAgeUnitsComboBox() + { + if(mAgeUnitsComboBox == null) + { + mAgeUnitsComboBox = new ComboBox<>(FXCollections.observableArrayList(AgeUnits.values())); + mAgeUnitsComboBox.getSelectionModel().select(mRetentionPreference.getAgeUnits()); + mAgeUnitsComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if(newValue != null) + { + mRetentionPreference.setAgeUnits(newValue); + } + }); + } + + return mAgeUnitsComboBox; + } + + /** + * Size units combo box control. + */ + public ComboBox getSizeUnitsComboBox() + { + if(mSizeUnitsComboBox == null) + { + mSizeUnitsComboBox = new ComboBox<>(FXCollections.observableArrayList(SizeUnits.values())); + mSizeUnitsComboBox.getSelectionModel().select(mRetentionPreference.getSizeUnits()); + mSizeUnitsComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if(newValue != null) + { + mRetentionPreference.setSizeUnits(newValue); + } + }); + } + + return mSizeUnitsComboBox; + } + + /** + * Age value text field. + */ + private IntegerTextField getAgeValueTextField() + { + if(mAgeValueTextField == null) + { + mAgeValueTextField = new IntegerTextField(); + mAgeValueTextField.set(mRetentionPreference.getAgeValue()); + mAgeValueTextField.textProperty().addListener((observable, oldValue, newValue) -> { + Integer value = mAgeValueTextField.get(); + + if(value != null) + { + mRetentionPreference.setAgeValue(value); + } + else + { + mAgeValueTextField.set(mRetentionPreference.getAgeValue()); + } + }); + //Monitor field for lost focus and restore a valid value if the user entered value is bad + mAgeValueTextField.focusedProperty().addListener((observable, oldValue, isFocused) -> { + if(!isFocused) + { + Integer value = mAgeValueTextField.get(); + + if(value == null || value <= 0) + { + mAgeValueTextField.setText(String.valueOf(mRetentionPreference.getAgeValue())); + } + } + }); + } + + return mAgeValueTextField; + } + + /** + * Directory size text field. + */ + private IntegerTextField getDirectorySizeTextField() + { + if(mDirectorySizeTextField == null) + { + mDirectorySizeTextField = new IntegerTextField(); + mDirectorySizeTextField.set(mRetentionPreference.getMaxDirectorySize()); + mDirectorySizeTextField.textProperty().addListener((observable, oldValue, newValue) -> { + Integer value = mDirectorySizeTextField.get(); + + if(value != null && value > 0) + { + mRetentionPreference.setMaxDirectorySize(value); + } + }); + //Monitor field for lost focus and restore a valid value if the user entered value is bad + mDirectorySizeTextField.focusedProperty().addListener((observable, oldValue, isFocused) -> { + if(!isFocused) + { + Integer value = mDirectorySizeTextField.get(); + + if(value == null || value <= 0) + { + mDirectorySizeTextField.setText(String.valueOf(mRetentionPreference.getMaxDirectorySize())); + } + } + }); + } + + return mDirectorySizeTextField; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/preference/directory/DirectoryPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/directory/DirectoryPreferenceEditor.java index 6649f244..97cdeeac 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/directory/DirectoryPreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/directory/DirectoryPreferenceEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,6 +64,11 @@ public class DirectoryPreferenceEditor extends PreferenceEditor implements IPref private Button mResetApplicationLogsButton; private Label mApplicationLogsPathLabel; + private Label mCallLabel; + private Button mChangeCallButton; + private Button mResetCallButton; + private Label mCallPathLabel; + private Label mConfigurationLabel; private Button mChangeConfigurationButton; private Button mResetConfigurationButton; @@ -172,6 +177,19 @@ public class DirectoryPreferenceEditor extends PreferenceEditor implements IPref mEditorPane.add(getResetApplicationLogsButton(), 3, row++); + GridPane.setMargin(getCallLabel(), new Insets(0, 10, 0, 0)); + mEditorPane.add(getCallLabel(), 0, row); + + GridPane.setMargin(getCallPathLabel(), new Insets(0, 10, 0, 0)); + mEditorPane.add(getCallPathLabel(), 1, row); + + GridPane.setMargin(getChangeCallButton(), new Insets(2, 10, 2, 0)); + mEditorPane.add(getChangeCallButton(), 2, row); + + GridPane.setMargin(getResetCallButton(), new Insets(2, 0, 2, 0)); + mEditorPane.add(getResetCallButton(), 3, row++); + + GridPane.setMargin(getConfigurationLabel(), new Insets(0, 10, 0, 0)); mEditorPane.add(getConfigurationLabel(), 0, row); @@ -442,6 +460,71 @@ public class DirectoryPreferenceEditor extends PreferenceEditor implements IPref return mApplicationLogsPathLabel; } + private Label getCallLabel() + { + if(mCallLabel == null) + { + mCallLabel = new Label("Calls"); + } + + return mCallLabel; + } + + private Button getChangeCallButton() + { + if(mChangeCallButton == null) + { + mChangeCallButton = new Button("Change..."); + mChangeCallButton.setOnAction(new EventHandler() + { + @Override + public void handle(ActionEvent event) + { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle("Select Calls Folder"); + directoryChooser.setInitialDirectory(mDirectoryPreference.getDirectoryCalls().toFile()); + Stage stage = (Stage)getChangeCallButton().getScene().getWindow(); + File selected = directoryChooser.showDialog(stage); + + if(selected != null) + { + mDirectoryPreference.setDirectoryCalls(selected.toPath()); + } + } + }); + } + + return mChangeCallButton; + } + + private Button getResetCallButton() + { + if(mResetCallButton == null) + { + mResetCallButton = new Button("Reset"); + mResetCallButton.setOnAction(new EventHandler() + { + @Override + public void handle(ActionEvent event) + { + mDirectoryPreference.resetDirectoryCall(); + } + }); + } + + return mResetCallButton; + } + + private Label getCallPathLabel() + { + if(mCallPathLabel == null) + { + mCallPathLabel = new Label(mDirectoryPreference.getDirectoryCalls().toString()); + } + + return mCallPathLabel; + } + private Label getConfigurationLabel() { if(mConfigurationLabel == null) @@ -904,6 +987,7 @@ public class DirectoryPreferenceEditor extends PreferenceEditor implements IPref { getApplicationRootPathLabel().setText(mDirectoryPreference.getDirectoryApplicationRoot().toString()); getApplicationLogsPathLabel().setText(mDirectoryPreference.getDirectoryApplicationLog().toString()); + getCallPathLabel().setText(mDirectoryPreference.getDirectoryCalls().toString()); getEventLogsPathLabel().setText(mDirectoryPreference.getDirectoryEventLog().toString()); getPlaylistPathLabel().setText(mDirectoryPreference.getDirectoryPlaylist().toString()); getRecordingPathLabel().setText(mDirectoryPreference.getDirectoryRecording().toString()); diff --git a/src/main/java/io/github/dsheirer/gui/preference/record/RecordPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/record/RecordPreferenceEditor.java deleted file mode 100644 index 2736325a..00000000 --- a/src/main/java/io/github/dsheirer/gui/preference/record/RecordPreferenceEditor.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.gui.preference.record; - -import io.github.dsheirer.gui.preference.PreferenceEditor; -import io.github.dsheirer.gui.preference.PreferenceEditorType; -import io.github.dsheirer.preference.record.RecordPreference; -import io.github.dsheirer.record.RecordFormat; -import jakarta.annotation.PostConstruct; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.geometry.Insets; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -/** - * Preference settings for recording - */ -public class RecordPreferenceEditor extends PreferenceEditor -{ - private final static Logger mLog = LoggerFactory.getLogger(RecordPreferenceEditor.class); - private RecordPreference mRecordPreference; - private GridPane mEditorPane; - private ComboBox mRecordFormatComboBox; - - public RecordPreferenceEditor() - { - } - - @PostConstruct - public void postConstruct() - { - mRecordPreference = getUserPreferences().getRecordPreference(); - HBox.setHgrow(getEditorPane(), Priority.ALWAYS); - getChildren().add(getEditorPane()); - } - - @Override - public PreferenceEditorType getPreferenceEditorType() - { - return PreferenceEditorType.AUDIO_RECORD; - } - - private GridPane getEditorPane() - { - if(mEditorPane == null) - { - mEditorPane = new GridPane(); - mEditorPane.setPadding(new Insets(10, 10, 10, 10)); - mEditorPane.setHgap(10); - mEditorPane.setVgap(10); - - Label label = new Label("Audio Recording Format:"); - mEditorPane.add(label, 0, 0); - - mEditorPane.add(getRecordFormatComboBox(), 1, 0); - } - - return mEditorPane; - } - - private ComboBox getRecordFormatComboBox() - { - if(mRecordFormatComboBox == null) - { - mRecordFormatComboBox = new ComboBox<>(); - mRecordFormatComboBox.getItems().addAll(RecordFormat.values()); - mRecordFormatComboBox.getSelectionModel().select(mRecordPreference.getAudioRecordFormat()); - mRecordFormatComboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() - { - @Override - public void changed(ObservableValue observable, RecordFormat oldValue, RecordFormat newValue) - { - mRecordPreference.setAudioRecordFormat(newValue); - } - }); - } - - return mRecordFormatComboBox; - } -} diff --git a/src/main/java/io/github/dsheirer/preference/PreferenceType.java b/src/main/java/io/github/dsheirer/preference/PreferenceType.java index 813c3cf3..1f31a918 100644 --- a/src/main/java/io/github/dsheirer/preference/PreferenceType.java +++ b/src/main/java/io/github/dsheirer/preference/PreferenceType.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,6 +36,7 @@ public enum PreferenceType PLAYBACK, RADIO_REFERENCE, RECORD, + RETENTION, TALKGROUP_FORMAT, TUNER; } diff --git a/src/main/java/io/github/dsheirer/preference/UserPreferences.java b/src/main/java/io/github/dsheirer/preference/UserPreferences.java index 08f1fedb..984dc81b 100644 --- a/src/main/java/io/github/dsheirer/preference/UserPreferences.java +++ b/src/main/java/io/github/dsheirer/preference/UserPreferences.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,6 +32,7 @@ import io.github.dsheirer.preference.playback.PlaybackPreference; import io.github.dsheirer.preference.playlist.PlaylistPreference; import io.github.dsheirer.preference.radioreference.RadioReferencePreference; import io.github.dsheirer.preference.record.RecordPreference; +import io.github.dsheirer.preference.retention.RetentionPreference; import io.github.dsheirer.preference.source.ChannelMultiFrequencyPreference; import io.github.dsheirer.preference.source.TunerPreference; import io.github.dsheirer.preference.swing.SwingPreference; @@ -78,6 +79,7 @@ public class UserPreferences implements Listener private PlaylistPreference mPlaylistPreference; private RadioReferencePreference mRadioReferencePreference; private RecordPreference mRecordPreference; + private RetentionPreference mRetentionPreference; private SwingPreference mSwingPreference = new SwingPreference(); private TalkgroupFormatPreference mTalkgroupFormatPreference; private TunerPreference mTunerPreference; @@ -216,6 +218,14 @@ public class UserPreferences implements Listener return mRecordPreference; } + /** + * Audio/call retention preferences + */ + public RetentionPreference getRetentionPreference() + { + return mRetentionPreference; + } + /** * MP3 preferences */ @@ -273,6 +283,7 @@ public class UserPreferences implements Listener mPlaylistPreference = new PlaylistPreference(this::receive, mDirectoryPreference); mRadioReferencePreference = new RadioReferencePreference(this::receive); mRecordPreference = new RecordPreference(this::receive); + mRetentionPreference = new RetentionPreference(this::receive); mTalkgroupFormatPreference = new TalkgroupFormatPreference(this::receive); mTunerPreference = new TunerPreference(this::receive); mVectorCalibrationPreference = new VectorCalibrationPreference(this::receive); diff --git a/src/main/java/io/github/dsheirer/preference/directory/DirectoryPreference.java b/src/main/java/io/github/dsheirer/preference/directory/DirectoryPreference.java index 4ef86832..b04fceb0 100644 --- a/src/main/java/io/github/dsheirer/preference/directory/DirectoryPreference.java +++ b/src/main/java/io/github/dsheirer/preference/directory/DirectoryPreference.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,6 +42,7 @@ public class DirectoryPreference extends Preference private static final String DIRECTORY_APPLICATION_ROOT = "SDRTrunk"; private static final String DIRECTORY_APPLICATION_LOG = "logs"; + private static final String DIRECTORY_CALL = "calls"; private static final String DIRECTORY_CONFIGURATION = "configuration"; private static final String DIRECTORY_EVENT_LOG = "event_logs"; private static final String DIRECTORY_JMBE = "jmbe"; @@ -52,6 +53,7 @@ public class DirectoryPreference extends Preference private static final String PREFERENCE_KEY_DIRECTORY_APPLICATION_ROOT = "directory.application.root"; private static final String PREFERENCE_KEY_DIRECTORY_APPLICATION_LOGS = "directory.application.logs"; + private static final String PREFERENCE_KEY_DIRECTORY_CALLS = "directory.event.calls"; private static final String PREFERENCE_KEY_DIRECTORY_CONFIGURATION = "directory.configuration"; private static final String PREFERENCE_KEY_DIRECTORY_EVENT_LOGS = "directory.event.logs"; private static final String PREFERENCE_KEY_DIRECTORY_JMBE = "directory.jmbe"; @@ -64,6 +66,7 @@ public class DirectoryPreference extends Preference private Path mDirectoryApplicationRoot; private Path mDirectoryApplicationLogs; + private Path mDirectoryCalls; private Path mDirectoryConfiguration; private Path mDirectoryEventLogs; private Path mDirectoryJmbe; @@ -125,6 +128,7 @@ public class DirectoryPreference extends Preference private void nullifyApplicationChildDirectories() { mDirectoryApplicationLogs = null; + mDirectoryCalls = null; mDirectoryEventLogs = null; mDirectoryJmbe = null; mDirectoryPlaylist = null; @@ -234,6 +238,40 @@ public class DirectoryPreference extends Preference notifyPreferenceUpdated(); } + /** + * Path to the folder for storing calls + */ + public Path getDirectoryCalls() + { + if(mDirectoryCalls == null) + { + mDirectoryCalls = getPath(PREFERENCE_KEY_DIRECTORY_CALLS, getDefaultCallDirectory()); + createDirectory(mDirectoryCalls); + } + + return mDirectoryCalls; + } + + /** + * Sets the path to the calls folder + */ + public void setDirectoryCalls(Path path) + { + mDirectoryCalls = path; + mPreferences.put(PREFERENCE_KEY_DIRECTORY_CALLS, path.toString()); + notifyPreferenceUpdated(); + } + + /** + * Removes a stored call directory preference so that the default path can be used again + */ + public void resetDirectoryCall() + { + mPreferences.remove(PREFERENCE_KEY_DIRECTORY_CALLS); + mDirectoryCalls = null; + notifyPreferenceUpdated(); + } + /** * Path to the folder for storing configuration files */ @@ -488,6 +526,14 @@ public class DirectoryPreference extends Preference return getDirectoryApplicationRoot().resolve(DIRECTORY_APPLICATION_LOG); } + /** + * Default calls directory + */ + public Path getDefaultCallDirectory() + { + return getDirectoryApplicationRoot().resolve(DIRECTORY_CALL); + } + /** * Default configuration directory */ diff --git a/src/main/java/io/github/dsheirer/preference/playback/PlaybackPreference.java b/src/main/java/io/github/dsheirer/preference/playback/PlaybackPreference.java index 46408892..d699f59a 100644 --- a/src/main/java/io/github/dsheirer/preference/playback/PlaybackPreference.java +++ b/src/main/java/io/github/dsheirer/preference/playback/PlaybackPreference.java @@ -20,9 +20,9 @@ package io.github.dsheirer.preference.playback; import io.github.dsheirer.audio.playbackfx.PlaybackView; -import io.github.dsheirer.gui.preference.playback.ToneFrequency; -import io.github.dsheirer.gui.preference.playback.ToneUtil; -import io.github.dsheirer.gui.preference.playback.ToneVolume; +import io.github.dsheirer.gui.preference.audio.playback.ToneFrequency; +import io.github.dsheirer.gui.preference.audio.playback.ToneUtil; +import io.github.dsheirer.gui.preference.audio.playback.ToneVolume; import io.github.dsheirer.preference.Preference; import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.sample.Listener; diff --git a/src/main/java/io/github/dsheirer/preference/retention/AgeUnits.java b/src/main/java/io/github/dsheirer/preference/retention/AgeUnits.java new file mode 100644 index 00000000..abba32a3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/retention/AgeUnits.java @@ -0,0 +1,49 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.preference.retention; + +import java.time.Duration; + +/** + * Age units + */ +public enum AgeUnits +{ + DAYS, + WEEKS, + MONTHS, + YEARS; + + /** + * Returns a duration value in milliseconds that represents the value in the current units. + * @param value to calculate + * @return milliseconds of duration representing the age unit and value. + */ + public long getDuration(int value) + { + return switch(this) + { + case DAYS -> Duration.ofDays(value).toMillis(); + case WEEKS -> Duration.ofDays(7).toMillis() * value; + case MONTHS -> Duration.ofDays(31).toMillis() * value; + case YEARS -> Duration.ofDays(365).toMillis() * value; + }; + } +} diff --git a/src/main/java/io/github/dsheirer/preference/retention/RetentionPolicy.java b/src/main/java/io/github/dsheirer/preference/retention/RetentionPolicy.java new file mode 100644 index 00000000..810c8da3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/retention/RetentionPolicy.java @@ -0,0 +1,68 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.preference.retention; + +import java.util.EnumSet; + +/** + * Retention (ie age-off) policy for managing call events and related audio recording files. + */ +public enum RetentionPolicy +{ + AGE("Age"), + SIZE("Directory Size"), + AGE_AND_SIZE("Age & Directory Size"); + + private String mLabel; + + /** + * Constructs an instance + * @param label to display for the entry + */ + RetentionPolicy(String label) + { + mLabel = label; + } + + public static EnumSet AGE_POLICIES = EnumSet.of(AGE, AGE_AND_SIZE); + public static EnumSet SIZE_POLICIES = EnumSet.of(SIZE, AGE_AND_SIZE); + + @Override + public String toString() + { + return mLabel; + } + + /** + * Indicates if the entry is an age-based retention policy. + */ + public boolean isAgePolicy() + { + return AGE_POLICIES.contains(this); + } + + /** + * Indicates if the entry is a size-based retention policy. + */ + public boolean isSizePolicy() + { + return SIZE_POLICIES.contains(this); + } +} diff --git a/src/main/java/io/github/dsheirer/preference/retention/RetentionPreference.java b/src/main/java/io/github/dsheirer/preference/retention/RetentionPreference.java new file mode 100644 index 00000000..17a97515 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/retention/RetentionPreference.java @@ -0,0 +1,255 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.preference.retention; + +import io.github.dsheirer.preference.Preference; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.sample.Listener; +import java.util.prefs.Preferences; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * User preferences for (audio) retention and age-off + */ +public class RetentionPreference extends Preference +{ + private static final String PREFERENCE_KEY_AUDIO_RETENTION_POLICY = "audio.retention.policy"; + private static final String PREFERENCE_KEY_AGE_UNITS = "call.age.units"; + private static final String PREFERENCE_KEY_AGE_VALUE = "call.age.value"; + private static final String PREFERENCE_KEY_SIZE_UNITS = "call.size.units"; + private static final String PREFERENCE_KEY_MAX_CALL_DIRECTORY_SIZE = "audio.max.directory.size.gb"; + private static final long GIGABYTES = 1024l * 1024l * 1024l; + private static final long MEGABYTES = 1024l * 1024l; + private final static Logger mLog = LoggerFactory.getLogger(RetentionPreference.class); + private Preferences mPreferences = Preferences.userNodeForPackage(RetentionPreference.class); + private RetentionPolicy mRetentionPolicy; + private AgeUnits mAgeUnits; + private Integer mAgeValue; + private SizeUnits mSizeUnits; + private Integer mMaxDirectorySize; + + /** + * Constructs this preference with an update listener + * @param updateListener to receive notifications whenever these preferences change + */ + public RetentionPreference(Listener updateListener) + { + super(updateListener); + } + + @Override + public PreferenceType getPreferenceType() + { + return PreferenceType.RETENTION; + } + + /** + * Retention policy to use for age-off of call events and related audio recordings. + * @return retention policy. + */ + public RetentionPolicy getRetentionPolicy() + { + if(mRetentionPolicy == null) + { + String raw = mPreferences.get(PREFERENCE_KEY_AUDIO_RETENTION_POLICY, null); + + if(raw != null) + { + try + { + mRetentionPolicy = RetentionPolicy.valueOf(raw); + } + catch(Exception e ) + { + //Do nothing ... use the default. + } + } + + if(mRetentionPolicy == null) + { + mRetentionPolicy = RetentionPolicy.AGE_AND_SIZE; //Default value + } + } + + return mRetentionPolicy; + } + + /** + * Sets the audio/call retention policy. + * @param retentionPolicy to use + */ + public void setRetentionPolicy(RetentionPolicy retentionPolicy) + { + mRetentionPolicy = retentionPolicy; + mPreferences.put(PREFERENCE_KEY_AUDIO_RETENTION_POLICY, retentionPolicy.name()); + notifyPreferenceUpdated(); + } + + /** + * Age units for call/audio age-off + * @return age units. + */ + public AgeUnits getAgeUnits() + { + if(mAgeUnits == null) + { + String raw = mPreferences.get(PREFERENCE_KEY_AGE_UNITS, null); + + if(raw != null) + { + try + { + mAgeUnits = AgeUnits.valueOf(raw); + } + catch(Exception e ) + { + //Do nothing ... use the default. + } + } + + if(mAgeUnits == null) + { + mAgeUnits = AgeUnits.DAYS; //Default value + } + } + + return mAgeUnits; + } + + /** + * Sets the age units. + * @param ageUnits to set + */ + public void setAgeUnits(AgeUnits ageUnits) + { + mAgeUnits = ageUnits; + mPreferences.put(PREFERENCE_KEY_AGE_UNITS, mAgeUnits.name()); + notifyPreferenceUpdated(); + } + + /** + * Age value + * @return age value. + */ + public int getAgeValue() + { + if(mAgeValue == null) + { + mAgeValue = mPreferences.getInt(PREFERENCE_KEY_AGE_VALUE, 30); + } + + return mAgeValue; + } + + /** + * Sets the age value. + * @param value to set + */ + public void setAgeValue(int value) + { + mPreferences.putInt(PREFERENCE_KEY_AGE_VALUE, value); + notifyPreferenceUpdated(); + } + + /** + * Maximum directory size in gigabytes (GB) + * @return size + */ + public int getMaxDirectorySize() + { + if(mMaxDirectorySize == null) + { + mMaxDirectorySize = mPreferences.getInt(PREFERENCE_KEY_MAX_CALL_DIRECTORY_SIZE, 5); + } + + return mMaxDirectorySize; + } + + /** + * Objective file system directory size. + * Note: value is adjusted for byte-2-kilobyte (1000 vs 1024) conversion. + * @return objective directory size. + */ + public long getObjectiveDirectorySize() + { + if(getSizeUnits() == SizeUnits.GB) + { + return getMaxDirectorySize() * GIGABYTES; + } + else + { + return getMaxDirectorySize() * MEGABYTES; + } + } + + /** + * Sets the maximum call directory size in gigabytes (GB) + * @param sizeGb for the directory. + */ + public void setMaxDirectorySize(int sizeGb) + { + mMaxDirectorySize = sizeGb; + mPreferences.putInt(PREFERENCE_KEY_MAX_CALL_DIRECTORY_SIZE, sizeGb); + notifyPreferenceUpdated(); + } + + /** + * Size units for directory sizing. + * @return size units or default of gigabytes. + */ + public SizeUnits getSizeUnits() + { + if(mSizeUnits == null) + { + String raw = mPreferences.get(PREFERENCE_KEY_SIZE_UNITS, null); + + if(raw != null) + { + try + { + mSizeUnits = SizeUnits.valueOf(raw); + } + catch(Exception e) + { + //Do nothing. + } + } + + if(mSizeUnits == null) + { + mSizeUnits = SizeUnits.GB; //default + } + } + + return mSizeUnits; + } + + /** + * Sets the size units for directory sizing. + * @param sizeUnits to set + */ + public void setSizeUnits(SizeUnits sizeUnits) + { + mSizeUnits = sizeUnits; + mPreferences.put(PREFERENCE_KEY_SIZE_UNITS, mSizeUnits.name()); + notifyPreferenceUpdated();; + } +} diff --git a/src/main/java/io/github/dsheirer/preference/retention/SizeUnits.java b/src/main/java/io/github/dsheirer/preference/retention/SizeUnits.java new file mode 100644 index 00000000..12f35689 --- /dev/null +++ b/src/main/java/io/github/dsheirer/preference/retention/SizeUnits.java @@ -0,0 +1,29 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.preference.retention; + +/** + * Size units + */ +public enum SizeUnits +{ + MB, + GB; +}