1723 Adds call event management and deletion by age or directory size, both options in the user preferences dialog.

This commit is contained in:
Dennis Sheirer
2024-01-26 02:35:54 -05:00
parent af2b80a066
commit 93f7122f57
29 changed files with 1251 additions and 269 deletions

View File

@@ -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
{

View File

@@ -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<AudioSegment>
{
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<AudioSegment>
private UserPreferences mUserPreferences;
private CallRepository mCallRepository;
private List<ICallEventListener> 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<AudioSegment>
@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<AudioSegment>
@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<AudioSegment>
//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<AudioSegment>
}
}
}
/**
* 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<Call> page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, threshold, requestPage1);
while(page.getTotalElements() > 0)
{
List<Call> 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<Call> page = mCallRepository.findCallsByEventTimeBetweenOrderByEventTime(start, now, pageable);
while(currentSize >= objectiveSize && page.getTotalElements() > 0)
{
List<Call> 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);
}
}
}
}

View File

@@ -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))
{

View File

@@ -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();
}
}

View File

@@ -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<Call,Long>
{
@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<Call> 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<Call> 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<Call> 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<Call> 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<Call> 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<Call> 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<Call> 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<Call> 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<Call> findCallsByEventTimeBetweenOrderByEventTime(long start, long end, Pageable pageable);
}

View File

@@ -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, Long>(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, Double>(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, String>(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, String>(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, String>(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, String>(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, String>(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, String>(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, Double>(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, String>(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, String>(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, Boolean>(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, Boolean>(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, Boolean>(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, Integer>(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);
});
}
}

View File

@@ -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<MediaPlaye
{
private static final String EMPTY = new String("EMPTY");
private static final String LOADED = new String("LOADED");
private static final String NO_FILE = new String("NO FILE");
private ObjectProperty<MediaPlayer> mMediaPlayer = new SimpleObjectProperty<>();
private StringProperty mFrequency = new SimpleStringProperty();
@@ -233,18 +235,31 @@ public class AudioPlaybackChannelController implements ChangeListener<MediaPlaye
{
if(mCall.getFile() != null)
{
//Create a new media player
mMediaPlayer.set(new MediaPlayer(new Media(new File(mCall.getFile()).toURI().toString())));
//Register status listener
mMediaPlayer.get().statusProperty().addListener(this);
//Register runnable for end-of-media event
mMediaPlayer.get().onEndOfMediaProperty().set(mEndOfMediaRunnable);
//Set audio channel balance
mMediaPlayer.get().balanceProperty().set(mBalance);
//Set the media player to auto-start replay
mMediaPlayer.get().setAutoPlay(true);
File file = new File(mCall.getFile());
mMediaPlayerStatus.set(LOADED);
if(file.exists())
{
try
{
MediaPlayer mediaPlayer = new MediaPlayer(new Media(file.toURI().toString()));
//Create a new media player
mMediaPlayer.set(mediaPlayer);
//Register status listener
mMediaPlayer.get().statusProperty().addListener(this);
//Register runnable for end-of-media event
mMediaPlayer.get().onEndOfMediaProperty().set(mEndOfMediaRunnable);
//Set audio channel balance
mMediaPlayer.get().balanceProperty().set(mBalance);
//Set the media player to auto-start replay
mMediaPlayer.get().setAutoPlay(true);
mMediaPlayerStatus.set(LOADED);
}
catch(MediaException me)
{
mMediaPlayer.set(null);
mMediaPlayerStatus.set(NO_FILE);
}
}
//Register a listener on the call's last updated property for update notifications.
mCall.lastUpdatedProperty().addListener(mCallUpdateListener);

View File

@@ -67,13 +67,13 @@ import io.github.dsheirer.gui.preference.DecodeEventViewPreferenceEditor;
import io.github.dsheirer.gui.preference.TalkgroupFormatPreferenceEditor;
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
import io.github.dsheirer.gui.preference.application.ApplicationPreferenceEditor;
import io.github.dsheirer.gui.preference.audio.call.CallManagementPreferenceEditor;
import io.github.dsheirer.gui.preference.audio.playback.PlaybackPreferenceEditor;
import io.github.dsheirer.gui.preference.audio.record.RecordPreferenceEditor;
import io.github.dsheirer.gui.preference.audio.retention.RetentionPreferenceEditor;
import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreferenceEditor;
import io.github.dsheirer.gui.preference.call.CallManagementPreferenceEditor;
import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor;
import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
import io.github.dsheirer.gui.preference.mp3.MP3PreferenceEditor;
import io.github.dsheirer.gui.preference.playback.PlaybackPreferenceEditor;
import io.github.dsheirer.gui.preference.record.RecordPreferenceEditor;
import io.github.dsheirer.gui.preference.tuner.TunerPreferenceEditor;
import io.github.dsheirer.jmbe.JmbeEditor;
import java.awt.GraphicsEnvironment;
@@ -618,16 +618,6 @@ public class SpringJavaFxBeanFactory
return new CallManagementPreferenceEditor();
}
/**
* Application preference editor
*/
@Bean
@Lazy
public MP3PreferenceEditor getMp3PreferenceEditor()
{
return new MP3PreferenceEditor();
}
/**
* Playback preference editor
*/
@@ -648,6 +638,16 @@ public class SpringJavaFxBeanFactory
return new RecordPreferenceEditor();
}
/**
* Audio/call retention preference editor
*/
@Bean
@Lazy
public RetentionPreferenceEditor getRetentionPreferenceEditor()
{
return new RetentionPreferenceEditor();
}
/**
* Decode event view preference editor
*/

View File

@@ -87,8 +87,9 @@ public class TableViewColumnController implements ListChangeListener
public void reset()
{
apply(mDefaultTableState);
//Clear the preference since we're at default state
mPreferences.put(mPreferencePrefix + PREFERENCE_KEY_TABLE_STATE, null);
mPreferences.remove(mPreferencePrefix + PREFERENCE_KEY_TABLE_STATE);
}
/**
@@ -133,6 +134,12 @@ public class TableViewColumnController implements ListChangeListener
}
}
//If the table names got screwed up in the table state, just add the columns all back in.
if(mTableView.getColumns().isEmpty())
{
mTableView.getColumns().addAll(columns);
}
//Clear and restore the sort ordering
mTableView.getSortOrder().clear();
for(TableState.SortedColumn sortedColumn: tableState.getSort())

View File

@@ -25,13 +25,13 @@ package io.github.dsheirer.gui.preference;
public enum PreferenceEditorType
{
APPLICATION("Application"),
AUDIO_PLAYBACK("Playback"),
AUDIO_RECORD("Record"),
AUDIO_DUPLICATE_MANAGEMENT("Duplicate/Streaming"),
AUDIO_CALL_RETENTION("Call Retention"),
CHANNEL_EVENT("Channel Events"),
DIRECTORY("Directories"),
JMBE_LIBRARY("JMBE Audio Library"),
AUDIO_MP3("MP3"),
AUDIO_RECORD("Record"),
AUDIO_PLAYBACK("Playback/Tones"),
AUDIO_CALL_MANAGEMENT("Call Management"),
TUNERS("Tuners"),
TALKGROUP_FORMAT("Talkgroup & Radio ID"),
VECTOR_CALIBRATION("Vector Calibration"),

View File

@@ -172,8 +172,8 @@ public class UserPreferencesEditor extends BorderPane
applicationItem.setExpanded(true);
TreeItem<String> 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);

View File

@@ -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()

View File

@@ -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;

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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
{

View File

@@ -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<RecordFormat> mRecordFormatComboBox;
private ComboBox<MP3Setting> mMP3SettingComboBox;
private ComboBox<InputAudioFormat> 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<RecordFormat> getRecordFormatComboBox()
{
if(mRecordFormatComboBox == null)
{
mRecordFormatComboBox = new ComboBox<>();
mRecordFormatComboBox.getItems().addAll(RecordFormat.values());
mRecordFormatComboBox.getSelectionModel().select(mRecordPreference.getAudioRecordFormat());
mRecordFormatComboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<RecordFormat>()
{
@Override
public void changed(ObservableValue<? extends RecordFormat> observable, RecordFormat oldValue, RecordFormat newValue)
{
mRecordPreference.setAudioRecordFormat(newValue);
}
});
}
return mRecordFormatComboBox;
}
private ComboBox<MP3Setting> getMP3SettingComboBox()
{
if(mMP3SettingComboBox == null)

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<RetentionPolicy> mRetentionPolicyComboBox;
private ComboBox<AgeUnits> mAgeUnitsComboBox;
private ComboBox<SizeUnits> 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<RetentionPolicy> 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<AgeUnits> 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<SizeUnits> 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;
}
}

View File

@@ -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<ActionEvent>()
{
@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<ActionEvent>()
{
@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());

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<RecordFormat> 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<RecordFormat> getRecordFormatComboBox()
{
if(mRecordFormatComboBox == null)
{
mRecordFormatComboBox = new ComboBox<>();
mRecordFormatComboBox.getItems().addAll(RecordFormat.values());
mRecordFormatComboBox.getSelectionModel().select(mRecordPreference.getAudioRecordFormat());
mRecordFormatComboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<RecordFormat>()
{
@Override
public void changed(ObservableValue<? extends RecordFormat> observable, RecordFormat oldValue, RecordFormat newValue)
{
mRecordPreference.setAudioRecordFormat(newValue);
}
});
}
return mRecordFormatComboBox;
}
}

View File

@@ -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;
}

View File

@@ -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<PreferenceType>
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<PreferenceType>
return mRecordPreference;
}
/**
* Audio/call retention preferences
*/
public RetentionPreference getRetentionPreference()
{
return mRetentionPreference;
}
/**
* MP3 preferences
*/
@@ -273,6 +283,7 @@ public class UserPreferences implements Listener<PreferenceType>
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);

View File

@@ -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
*/

View File

@@ -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;

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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;
};
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<RetentionPolicy> AGE_POLICIES = EnumSet.of(AGE, AGE_AND_SIZE);
public static EnumSet<RetentionPolicy> 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<PreferenceType> 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();;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.preference.retention;
/**
* Size units
*/
public enum SizeUnits
{
MB,
GB;
}