diff options
-rw-r--r-- | soundchan.properties.example | 20 | ||||
-rw-r--r-- | src/main/java/soundchan/BotListener/BotListener.java | 51 | ||||
-rw-r--r-- | src/main/java/soundchan/BotListener/MediaWatcher.java | 147 | ||||
-rw-r--r-- | src/main/java/soundchan/BotListener/MediaWatcherListener.java | 12 | ||||
-rw-r--r-- | src/main/java/soundchan/LocalAudioManager.java | 52 |
5 files changed, 257 insertions, 25 deletions
diff --git a/soundchan.properties.example b/soundchan.properties.example index 0655076..b600032 100644 --- a/soundchan.properties.example +++ b/soundchan.properties.example @@ -1,20 +1,30 @@ +// Example properties file for SoundChan +// +// Flag conditions are enabled with any of the following values: +// true, on, enable, yes, 1 +// any other string (or empty) will leave the condition disabled + + //The Bot Token you will have received from the discord developers page botToken=BOT_TOKEN_FROM_DISCORD //The local file path to the directory of your sounds. Don't forget to escape your slashes localFilePath=C:\\PATH\\TO\\SOUNDS\\DIRECTORY +//Flag for watching the sound directory for changes +watchLocalFilePath=FLAG_CONDITION + //The user for the followingUser=USERNAME -//Flag conditions are enabled with any of the following values: -// true, on, enable, yes, 1 -//any other string (or empty) will leave the condition disabled - //If you want SoundChan to play an audio file whit their name when a user joins the channel or have that information come from below file //This is a flag condition -audioOnUserJoin=on/off +audioOnUserJoin=FLAG_CONDITION //The file where users and sound clips are related, see usersound.properties.example for more info //If this is not set, it will default to usersounds.properties userAudioFilePath=C:\\PATH\\TO\\USER\\SOUND\\FILE + +//Flag for watching the user sound file for changes +//If audioOnUserJoin is not enabled, then this will do nothing +watchUserSoundFile=FLAG_CONDITION
\ No newline at end of file diff --git a/src/main/java/soundchan/BotListener/BotListener.java b/src/main/java/soundchan/BotListener/BotListener.java index 415e350..b8b4bad 100644 --- a/src/main/java/soundchan/BotListener/BotListener.java +++ b/src/main/java/soundchan/BotListener/BotListener.java @@ -19,10 +19,12 @@ import net.dv8tion.jda.core.hooks.ListenerAdapter; import net.dv8tion.jda.core.managers.AudioManager; import soundchan.*; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.nio.file.WatchEvent; +import java.sql.SQLOutput; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; public class BotListener extends ListenerAdapter{ @@ -32,6 +34,7 @@ public class BotListener extends ListenerAdapter{ private final AudioPlayerManager playerManager; private final Map<Long, GuildMusicManager> musicManagers; private BotListenerHelpers helper = new BotListenerHelpers(); + private Map<String, Future<?> > otherTasks; // From configuration file private static String followingUser; @@ -56,17 +59,35 @@ public class BotListener extends ListenerAdapter{ localFilePath = properties.getProperty("localFilePath"); followingUser = properties.getProperty("followingUser"); audioOnUserJoin = settingEnableCheck(properties.getProperty("audioOnUserJoin")); + otherTasks = new HashMap<>(); if(audioOnUserJoin) { String userAudioPath = properties.getProperty("userAudioFilePath"); if(userAudioPath == null || userAudioPath.contentEquals("")) { - localManager = new LocalAudioManager(localFilePath, "usersound.properties"); + userAudioPath = "usersound.properties"; } - else { - localManager = new LocalAudioManager(localFilePath, userAudioPath); + localManager = new LocalAudioManager(localFilePath, userAudioPath); + + if(settingEnableCheck(properties.getProperty("watchUserSoundFile"))) { + addWatcherTask(new MediaWatcherListener() { + @Override + public void onWatchEvent(WatchEvent event) { + localManager.UpdateUserAudio(); + } + }, userAudioPath, "watchUserSoundFile", false); } } else localManager = new LocalAudioManager(localFilePath); + + if(settingEnableCheck(properties.getProperty("watchLocalFilePath"))) { + addWatcherTask(new MediaWatcherListener() { + @Override + public void onWatchEvent(WatchEvent event) { + localManager.UpdateFiles(); + } + }, localFilePath, "watchLocalFilePath", true); + } + } private synchronized GuildMusicManager getGuildAudioPlayer() { @@ -425,6 +446,8 @@ public class BotListener extends ListenerAdapter{ * @return True if it matches a value to enable, False otherwise */ private static boolean settingEnableCheck(String value) { + if(value == null) + return false; value = value.toLowerCase(); if(value.contentEquals("true") || value.contentEquals("1") || value.contentEquals("yes") || value.contentEquals("on") || @@ -434,4 +457,18 @@ public class BotListener extends ListenerAdapter{ return false; } + /** + * Adds a new MediaWatcher to the list of running tasks + * @param listener Listener that will get callback during watching of media + * @param filepath Path to either directory or file + * @param taskName Thing to name task as + * @param watchSubDirs Also watch any subdirectories in the given directory (doesn't do anything if watching a file) + */ + private void addWatcherTask(@NotNull MediaWatcherListener listener, String filepath, String taskName, boolean watchSubDirs) { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + MediaWatcher watcher = new MediaWatcher(listener, filepath, watchSubDirs); + otherTasks.put(taskName, executorService.submit(watcher)); + executorService.shutdown(); + } + } diff --git a/src/main/java/soundchan/BotListener/MediaWatcher.java b/src/main/java/soundchan/BotListener/MediaWatcher.java new file mode 100644 index 0000000..2784eec --- /dev/null +++ b/src/main/java/soundchan/BotListener/MediaWatcher.java @@ -0,0 +1,147 @@ +package soundchan.BotListener; + +import java.io.File; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; + +import static java.lang.Thread.sleep; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + +public class MediaWatcher implements Runnable { + + private MediaWatcherListener listener; + private String mediaFilename; + private Path mediaDir; + private WatchService watchService; + private WatchKey watchKey; + private ArrayList<WatchKey> subDirKeys; + private boolean isDirectory; + private int sleepTime = 5000; + + @SuppressWarnings("unchecked") + static <T> WatchEvent<T> cast(WatchEvent<?> event) { + return (WatchEvent<T>) event; + } + + /** + * Creates a MediaWatcher, which monitors changes to files either within a directory or for a specific file. + * Defaults to scanning every 5 seconds. + * @param listener Object that will get a callback when there is a watch event + * @param filepath Path to either directory or specific file + * @param watchSubDirs Also watch any subdirectories in the given directory (doesn't do anything if watching a file) + */ + public MediaWatcher(MediaWatcherListener listener, String filepath, boolean watchSubDirs) { + this.listener = listener; + startWatchService(filepath, watchSubDirs); + } + + /** + * Creates a MediaWatcher, which monitors changes to files either within a directory or for a specific file. + * @param listener Object that will get a callback when there is a watch event + * @param filepath Path to either directory or specific file + * @param watchSubDirs Also watch any subdirectories in the given directory (doesn't do anything if watching a file) + * @param sleepTime How long to put the scanner thread to sleep between rescans (time in milliseconds) + */ + public MediaWatcher(MediaWatcherListener listener, String filepath, boolean watchSubDirs, int sleepTime) { + this.listener = listener; + this.sleepTime = sleepTime; + startWatchService(filepath, watchSubDirs); + } + + /** + * Sets up watch service for the file/directory + * @param filepath Path to file or directory to be scanned + * @param watchSubDirs Also watch any subdirectories in the given directory (doesn't do anything if watching a file) + */ + private void startWatchService(String filepath, boolean watchSubDirs) { + File mediaFile = new File(filepath); + this.mediaFilename = mediaFile.getName(); + if(mediaFile.isFile()) { + try { + this.mediaDir = mediaFile.getCanonicalFile().getParentFile().toPath(); + } catch (IOException e) { + System.out.println("Error getting parent path of " + mediaFilename); + } + isDirectory = false; + } else if(mediaFile.isDirectory()) { + this.mediaDir = mediaFile.toPath(); + isDirectory = true; + subDirKeys = new ArrayList<>(); + } + try { + this.watchService = FileSystems.getDefault().newWatchService(); + this.watchKey = mediaDir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + if(isDirectory && watchSubDirs) { + Files.walkFileTree(mediaDir, new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + try { + WatchKey temp = dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + subDirKeys.add(temp); + return FileVisitResult.CONTINUE; + } catch (IOException e) { + System.out.println("Error setting up watch service with sub dirs in " + filepath); + return FileVisitResult.SKIP_SUBTREE; + } + } + }); + } + } catch (IOException e) { + System.out.println("Error setting up watcher for " + filepath); + } + } + + /** + * Called by an executor, checks for changes to file(s) + */ + public void run() { + try { + while(true) { + WatchKey key = watchService.take(); // Wait for an event to happen + + // Check this event happened in a place we are monitoring, otherwise ignore it + if(!isDirectory && this.watchKey != key) { + System.out.println("Error with WatchKey"); + continue; + } else if(isDirectory) { + if(this.watchKey != key) { // Our event doesn't happen in the root of the sounds directory + boolean noKeyMatch = true; + for(WatchKey subKey : subDirKeys) { // Check if it happened in on of the sub directories + if(subKey == key) { + noKeyMatch = false; + break; + } + } + if(noKeyMatch) { + System.out.println("Error with WatchKey"); + continue; + } + } + } + + for(WatchEvent<?> event : key.pollEvents()) { + WatchEvent<Path> pathEvent = cast(event); + if(isDirectory) { + listener.onWatchEvent(event); + } else { + if(pathEvent.context().endsWith(mediaFilename)) { + listener.onWatchEvent(event); + } + } + } + + if(!key.reset()) { + break; + } + + sleep(sleepTime); + } + } catch(InterruptedException e) { + return; + } + } +} diff --git a/src/main/java/soundchan/BotListener/MediaWatcherListener.java b/src/main/java/soundchan/BotListener/MediaWatcherListener.java new file mode 100644 index 0000000..8c146f6 --- /dev/null +++ b/src/main/java/soundchan/BotListener/MediaWatcherListener.java @@ -0,0 +1,12 @@ +package soundchan.BotListener; + +import java.nio.file.WatchEvent; + +public interface MediaWatcherListener { + + /** + * Called by MediaWatcher when there is a file event. Any created, deleted, or modified events will be passed for use. + * @param event The type of event that triggered the update. + */ + void onWatchEvent(WatchEvent event); +} diff --git a/src/main/java/soundchan/LocalAudioManager.java b/src/main/java/soundchan/LocalAudioManager.java index 28d25b9..ea5b317 100644 --- a/src/main/java/soundchan/LocalAudioManager.java +++ b/src/main/java/soundchan/LocalAudioManager.java @@ -17,11 +17,12 @@ public class LocalAudioManager { public Map<String, String> filenameDict; public Map<String, String> usernameDict; private String filepath; + private String userSoundFilepath; public LocalAudioManager(String filepath_in){ filepath = filepath_in; - filenameDict = new HashMap<>(); - PopulateFiles(); + userSoundFilepath = null; + filenameDict = PopulateFiles(); } /** @@ -31,10 +32,9 @@ public class LocalAudioManager { */ public LocalAudioManager(String filepath_in, String userSoundFile) { filepath = filepath_in; - filenameDict = new HashMap<>(); - usernameDict = new HashMap<>(); - PopulateFiles(); - MapUserAudio(userSoundFile); + userSoundFilepath = userSoundFile; + filenameDict = PopulateFiles(); + usernameDict = MapUserAudio(); } /** @@ -90,31 +90,57 @@ public class LocalAudioManager { channel.sendMessage(toPrint).queue(); } - private void PopulateFiles(){ + /** + * Updates the map of sound files + */ + public void UpdateFiles() { + filenameDict = PopulateFiles(); + } + + /** + * Updates the map of usernames to sound files + */ + public void UpdateUserAudio() { + if(userSoundFilepath != null | userSoundFilepath.contentEquals("")) { + usernameDict = MapUserAudio(); + } + } + + /** + * Creates a map of the sounds in the sound directory + * @return A map with the filename (without extension) is the key for the filename (with extension) + */ + private Map<String, String> PopulateFiles(){ File folder = new File(filepath); File[] listOfFiles = folder.listFiles(); + Map<String, String> fileDict = new HashMap<>(); + for (File file : listOfFiles) { if (file.isFile()) { String filename = file.getName(); - filenameDict.put(filename.substring(0, filename.indexOf('.')), filename); + fileDict.put(filename.substring(0, filename.indexOf('.')), filename); } } + return fileDict; } /** * Reads in users and their respective sounds from file, then builds a map of users to the filenames. This assumes * filenames for the sounds are valid, but doesn't check for them. - * @param userSoundFile The file (with path if required) with listing of users and the sounds to play when they join + * @return A map with the usernames as the keys for the filename of the sound */ - private void MapUserAudio(String userSoundFile) { - Properties userSoundProp = LoadProperties(userSoundFile); + private Map<String, String> MapUserAudio() { + Properties userSoundProp = LoadProperties(userSoundFilepath); Set<String> users = userSoundProp.stringPropertyNames(); + + Map<String, String> userDict = new HashMap<>(); + for(String user : users) { String soundFile = userSoundProp.getProperty(user); - usernameDict.put(user, soundFile); + userDict.put(user, soundFile); } - + return userDict; } /** |