C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
//Reference: Ionic.Zip.Reduced
using Ionic.Crc;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
namespace Oxide.Plugins {
[Info("Tape Library", "Nikedemos", "0.0.1")]
[Description("Provides the foundation for downloading, storing, loading, saving and converting OGG files for playable tapes and ambient noises")]
public class TapeLibrary : RustPlugin {
private static TapeLibrary Instance;
private static ulong CommunityEntityInstanceNetID = 0;
#region HOOK SUBSCRIPTIONS
void OnServerInitialized() {
Instance = this;
CommunityEntity.ServerInstance.EnableGlobalBroadcast(true);
CommunityEntityInstanceNetID = CommunityEntity.ServerInstance.net.ID.Value;
OggVorbis.OnServerInitialized();
AudioDirectoryWatcher.OnServerInitialized();
TapeFactory.OnServerInitialized();
WebDownloader.OnServerInitialized();
AddCovalenceCommand(CMD_DOWNLOAD, nameof(CommandDownload));
AddCovalenceCommand(CMD_GIVE, nameof(CommandGiveTape));
}
void Unload() {
AudioDirectoryWatcher.Unload();
OggVorbis.Unload();
TapeFactory.Unload();
WebDownloader.Unload();
Instance = null;
CommunityEntityInstanceNetID = 0;
}
#endregion
#region API/CMD SHARED
[HookMethod(nameof(TryRecordOnExistingTapeByPartialName))]
public uint TryRecordOnExistingTapeByPartialName(string partialFilename, Item tapeItem, ulong userID = 0, ulong skinID = 0) {
if (!TapeFactory.IsValidTape(tapeItem)) {
return 0;
}
var foundMatchingWrapper = TryFindOggByPartialName(partialFilename);
if (foundMatchingWrapper == null) {
return 0;
}
TapeFactory.TryApplyContentsToCassette(foundMatchingWrapper.Crc32, tapeItem, userID, skinID, foundMatchingWrapper.Filename);
return foundMatchingWrapper.Crc32;
}
[HookMethod(nameof(TryProduceAndGiveTapeByPartialName))]
public Item TryProduceAndGiveTapeByPartialName(string partialFilename, BasePlayer player, bool forceLongTape = true, ulong skinID = 0) {
var tryProduceTape = TryProduceRecordedTapeByName(partialFilename, forceLongTape, skinID, player.userID);
if (tryProduceTape == null) {
return null;
}
player.GiveItem(tryProduceTape, BaseEntity.GiveItemReason.PickedUp);
return tryProduceTape;
}
[HookMethod(nameof(TryFindOggByPartialName))]
public OggVorbis.OggWrapper TryFindOggByPartialName(string partialFilename) {
OggVorbis.OggWrapper foundMatchingWrapper = null;
string firstArgToLower = partialFilename.ToLower();
foreach (var entry in OggVorbis.CacheOggFilesByFilename) {
if (!entry.Value.IsValid) {
continue;
}
if (entry.Key.ToLower().Contains(firstArgToLower)) {
foundMatchingWrapper = entry.Value;
break;
}
}
return foundMatchingWrapper;
}
[HookMethod(nameof(TryFindOggByID))]
public OggVorbis.OggWrapper TryFindOggByID(uint crc32) {
OggVorbis.OggWrapper foundMatchingWrapper;
if (!OggVorbis.CacheOggFilesByDataCRC.TryGetValue(crc32, out foundMatchingWrapper)) {
return null;
}
if (!foundMatchingWrapper.IsValid) {
return null;
}
return foundMatchingWrapper;
}
[HookMethod(nameof(TryProduceRecordedTapeByName))]
public Item TryProduceRecordedTapeByName(string partialFilename, bool forceLongTape, ulong skinID, ulong userID) {
var foundMatchingWrapper = TryFindOggByPartialName(partialFilename);
if (foundMatchingWrapper == null) {
return null;
}
return TapeFactory.TryProduceTape(foundMatchingWrapper, forceLongTape, skinID, userID);
}
[HookMethod(nameof(ProduceKnownTape))]
public Item ProduceKnownTape(uint crc32, ulong userID, ulong skinID, string text) {
return TapeFactory.ProduceTapeWithKnownID(crc32, userID, skinID, text);
}
[HookMethod(nameof(TryProduceRecordedTapeByID))]
public Item TryProduceRecordedTapeByID(uint crc32, bool forceLongTape, ulong skinID, ulong userID, bool skipIDCheck = false) {
var foundMatchingWrapper = TryFindOggByID(crc32);
if (foundMatchingWrapper == null) {
return null;
}
return TapeFactory.TryProduceTape(foundMatchingWrapper, forceLongTape, skinID, userID);
}
[HookMethod(nameof(TryProduceAndGiveTapeByID))]
public Item TryProduceAndGiveTapeByID(uint crc32, BasePlayer player, bool forceLongTape, ulong skinID) {
var tryProduceTape = TryProduceRecordedTapeByID(crc32, forceLongTape, skinID, player.userID);
if (tryProduceTape == null) {
return null;
}
player.GiveItem(tryProduceTape, BaseEntity.GiveItemReason.PickedUp);
return tryProduceTape;
}
[HookMethod(nameof(RequestOggDownloadToDirectory))]
public void RequestOggDownloadToDirectory(string url, int existingFileHandling = 0, Action<bool, string, string> actionOnDone = null) {
if (!WebDownloader.IsURLValid(url)) {
if (actionOnDone != null) {
actionOnDone(false, url, "ERROR: This does not appear to be a valid URL");
}
return;
}
WebDownloader.TryEnqueue(url, (WebDownloader.ExistingFileHandling)existingFileHandling, null, null, actionOnDone);
}
#endregion
#region CMD
public const string CMD_DOWNLOAD = "tl.download";
public const string CMD_DUMP = "tl.dump";
public const string CMD_GIVE = "tl.give";
private void CommandGiveTape(IPlayer iplayer, string command, string[] args) {
if (!OggVorbis.CacheOggFilesByDataCRC.Any()) {
iplayer.Reply("There doesn't seem to be any Tapes in the Library at the moment.");
return;
}
if (args.Length < (iplayer.IsServer ? 2 : 1)) {
StringBuilder buildList = new StringBuilder();
foreach (var entry in OggVorbis.CacheOggFilesByDataCRC) {
buildList.Append(" ");
buildList.Append(entry.Key);
buildList.Append(" : ");
buildList.Append(entry.Value.Filename);
if (!entry.Value.IsValid) {
buildList.Append(" (INVALID)");
}
buildList.Append("\n");
}
iplayer.Reply($"USAGE: {CMD_GIVE} [ID or partial filename] [partial player name or full steam ID].If a player executes it in the chat or console in-game and they don't specify a recipient, it will be given to the player executing this command.Executing from the server console requires specifying the player.\nHere's a list of available IDs and their corresponding filenames:\n\n{buildList}");
return;
}
BasePlayer playerToBequeef;
if (args.Length > 1) {
playerToBequeef = covalence.Players.FindPlayer(args[1])?.Object as BasePlayer ?? null;
}
else {
playerToBequeef = iplayer.Object as BasePlayer;
}
if (playerToBequeef == null) {
iplayer.Reply($"No player matching \"{args[1]}\" was found");
return;
}
uint crc32;
Item productedTape;
if (!uint.TryParse(args[0], out crc32)) {
productedTape = TryProduceAndGiveTapeByPartialName(args[0], playerToBequeef);
}
else {
productedTape = TryProduceAndGiveTapeByID(crc32, playerToBequeef, true, 0);
}
if (productedTape == null) {
iplayer.Reply($"No valid Tapes match the partial name or exact ID \"{args[0]}\".Type {CMD_GIVE} with no arguments to list all available ones.");
return;
}
iplayer.Reply($"{playerToBequeef.displayName} ({playerToBequeef.userID}) was given a new Tape \"{productedTape.name}\"");
}
private void CommandDownload(IPlayer iplayer, string command, string[] args) {
if (args.Length == 0) {
iplayer.Message("Please provide the URL of the OGG file to download.");
return;
}
if (!WebDownloader.IsURLValid(args[0])) {
iplayer.Message("This does not appear to be a valid URL");
return;
}
Item tapeItem = null;
var player = iplayer.Object as BasePlayer;
if (player != null) {
var activeItem = player.GetActiveItem();
if (TapeFactory.IsValidTape(activeItem)) {
tapeItem = activeItem;
}
}
int existingFileHandling = 0;
if (args.Length > 1) {
if (!int.TryParse(args[1], out existingFileHandling)) {
existingFileHandling = 0;
}
}
iplayer.Message($"Requesting download of {args[0]} with existing file handling: {(WebDownloader.ExistingFileHandling)existingFileHandling}, please wait...");
WebDownloader.TryEnqueue(args[0], (WebDownloader.ExistingFileHandling)existingFileHandling, iplayer, tapeItem);
}
#endregion
#region OGG VORBIS
public static class OggVorbis {
public static byte[] StringBytes;
public static Dictionary<string, OggWrapper> CacheOggFilesByFilename;
public static Dictionary<ulong, OggWrapper> CacheOggFilesByDataCRC;
public static CRC32 Crc32;
//quick lookup for that string's crc32
public static Dictionary<string, uint> CacheStringToCRC32;
public static void OnServerInitialized() {
CacheStringToCRC32 = new Dictionary<string, uint>();
CacheOggFilesByFilename = new Dictionary<string, OggWrapper>();
CacheOggFilesByDataCRC = new Dictionary<ulong, OggWrapper>();
Crc32 = new CRC32();
}
public static void Unload() {
CacheStringToCRC32 = null;
CacheOggFilesByFilename = null;
CacheOggFilesByDataCRC = null;
Crc32 = null;
}
public class OggWrapper {
public string Filename;
public DateTime DateCreated;
public DateTime DateModified;
public byte[] RawData;
public float SizeInMegabytes = 0F;
public double AudioLength = 0D;
public uint Crc32 = 0;
public int SampleRate = -1;
public int BitRate = -1;
public bool IsValidLength = false;
public bool IsValidSize = false;
public bool IsValid = false;
public string Comments = string.Empty;
public override string ToString() {
return $"{Filename}.ogg (CRC32: {Crc32}): {(IsValid ? "OK" : "ERROR")} ; SIZE : {SizeInMegabytes} MB ({(IsValidSize ? "V" : "X")}) ; DURATION: {AudioLength} s ({(IsValidLength ? "V" : "X")}) ; SAMPLE RATE : {SampleRate} ; BIT RATE : {BitRate}; COMMENTS: {Comments}";
}
}
public static uint GetCRC32(byte[] data) {
Crc32.Reset();
Crc32.SlurpBlock(data, 0, data.Length);
Crc32.UpdateCRC((byte)FileStorage.Type.ogg);
return (uint)Crc32.Crc32Result;
}
public static string ReadOGGComments(byte[] data) {
StringBuilder output = new StringBuilder();
int pos = 0;
int length = data.Length;
// Read OGG Vorbis Header
while (pos < length && data[pos] == 0x01) {
pos += 27;
// skip over header bytes
}
// Read Vorbis Comment Header
if (pos < length && data[pos] == 0x03) {
pos += 7;
// skip over header bytes
// Read Vorbis Comment fields
int commentLength = BitConverter.ToInt32(data, pos);
pos += 4;
for (int i = 0; i < commentLength; i++) {
int len = BitConverter.ToInt32(data, pos);
pos += 4;
string field = System.Text.Encoding.UTF8.GetString(data, pos, len);
pos += len;
// Check for key/value pair format
int index = field.IndexOf('=');
if (index >= 0) {
string key = field.Substring(0, index);
string value = field.Substring(index + 1);
output.AppendLine(key + ": " + value);
}
else {
output.AppendLine(field);
}
}
}
return output.ToString();
}
public static Tuple<int, int> GetSampleRateBitRateAndChannelCount(byte[] oggData) {
if (oggData.IsNullOrEmpty()) {
return new Tuple<int, int>(-1, -1);
}
if (oggData.Length < 28 + 12 + 4 + 4 + 4) {
return new Tuple<int, int>(-1, -1);
}
using (var stream = new MemoryStream(oggData)) {
using (var reader = new BinaryReader(stream)) {
// Read the first 28 bytes, which is the header of the Ogg file
byte[] header = reader.ReadBytes(28);
// Check if the header starts with "OggS"
if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') {
//return a default tuple
return new Tuple<int, int>(-1, -1);
}
//skip 12 bytes...reader.ReadBytes(12);
// Read the sample rate from the header
int sampleRate = reader.ReadInt32();
//skip 4 bytes...reader.ReadBytes(4);
// Read the bit rate from the header
int bitRate = reader.ReadInt32();
return new Tuple<int, int>(sampleRate, bitRate);
}
}
}
//TODO NEXT VERSION, HONESTLY CAN'T BE BOTHERED WITH ALL THE EDGE CASES NOW
/*
public static Dictionary<string, string> GetOggVorbisMetadata(byte[] oggData)
{
int offsetOfKeyValuePairCount = 0;
for (var b = 0; b < oggData.Length-4; b++)
{
if (oggData[b] != 'e' || oggData[b +1] != 'n' || oggData[b +2] != 't' || oggData[b +3] != ')')
{
continue;
}
offsetOfKeyValuePairCount = b + 4;
}
if (offsetOfKeyValuePairCount == 0)
{
return null;
}
if (offsetOfKeyValuePairCount > oggData.Length-1)
{
return null;
}
int keyValuePairCount = oggData[offsetOfKeyValuePairCount];
int currentOffset = offsetOfKeyValuePairCount + 1;
Dictionary<string, string> result = new Dictionary<string, string>();
return result;
}*/
public static uint GetCRC32(string data) {
uint result;
if (!CacheStringToCRC32.TryGetValue(data, out result)) {
StringBytes = Encoding.UTF8.GetBytes(data);
result = GetCRC32(StringBytes);
StringBytes = null;
CacheStringToCRC32.Add(data, result);
}
return result;
}
public static OggWrapper TryLoadNew(string fullPath) {
if (!File.Exists(fullPath)) {
return null;
}
var newOggFile = new OggWrapper();
if (!TryPopulateOggWrapperFromPathAndAddToCachesIfSuccess(fullPath, newOggFile)) {
return null;
}
return newOggFile;
}
public static OggWrapper TryUpdateExisting(string fullPath) {
if (!File.Exists(fullPath)) {
return null;
}
var filename = Path.GetFileNameWithoutExtension(fullPath);
OggWrapper maybeExisting;
if (!CacheOggFilesByFilename.TryGetValue(filename, out maybeExisting)) {
return null;
}
if (!TryPopulateOggWrapperFromPathAndAddToCachesIfSuccess(fullPath, maybeExisting)) {
return null;
}
return maybeExisting;
}
public static OggWrapper TryRenameExisting(string oldFullPath, string newFullPath) {
var oldFilename = Path.GetFileNameWithoutExtension(oldFullPath);
OggWrapper maybeExisting;
if (!CacheOggFilesByFilename.TryGetValue(oldFilename, out maybeExisting)) {
return null;
}
var newFilename = Path.GetFileNameWithoutExtension(newFullPath);
maybeExisting.Filename = newFilename;
//remove from cache under old name...CacheOggFilesByFilename.Remove(oldFilename);
//and add to cache under new name
CacheOggFilesByFilename.Add(maybeExisting.Filename, maybeExisting);
return maybeExisting;
}
public static uint TryForgetExisting(string fullPath) {
var filename = Path.GetFileNameWithoutExtension(fullPath);
OggWrapper maybeExisting;
if (!CacheOggFilesByFilename.TryGetValue(filename, out maybeExisting)) {
return 0;
}
var crc32 = maybeExisting.Crc32;
maybeExisting.RawData = null;
//and remove from caches
CacheOggFilesByFilename.Remove(filename);
RemoveFromCrc32CacheIfExists(crc32);
return crc32;
}
public static void RemoveFromCrc32CacheIfExists(uint crc32) {
if (!CacheOggFilesByDataCRC.ContainsKey(crc32)) {
return;
}
CacheOggFilesByDataCRC.Remove(crc32);
}
public static bool TryPopulateOggWrapperFromPathAndAddToCachesIfSuccess(string fullPath, OggWrapper oggWrapper) {
bool result = TryPopulateOggWraperFromPath(fullPath, oggWrapper);
if (result) {
CacheOggFilesByFilename.Add(oggWrapper.Filename, oggWrapper);
CacheOggFilesByDataCRC.Add(oggWrapper.Crc32, oggWrapper);
}
return result;
}
public static float ByteToMegabyte(ulong byteSize) {
return (float)byteSize / 1024f / 1024f;
}
public static float ByteToMegabyte(int byteSize) {
return (float)byteSize / 1024f / 1024f;
}
public static bool TryPopulateOggWraperFromPath(string fullPath, OggWrapper oggWrapper) {
oggWrapper.IsValidLength = false;
oggWrapper.IsValidSize = false;
oggWrapper.IsValid = false;
oggWrapper.AudioLength = 0;
oggWrapper.SizeInMegabytes = 0;
oggWrapper.SampleRate = -1;
oggWrapper.BitRate = -1;
oggWrapper.Filename = Path.GetFileNameWithoutExtension(fullPath);
oggWrapper.DateCreated = File.GetCreationTime(fullPath);
oggWrapper.DateModified = File.GetLastWriteTime(fullPath);
oggWrapper.RawData = File.ReadAllBytes(fullPath);
oggWrapper.Comments = string.Empty;
uint previousCrcData = oggWrapper.Crc32;
oggWrapper.Crc32 = GetCRC32(oggWrapper.RawData);
//remove from old cache, only data crc32 since the name is what's common...if (previousCrcData != default(uint))
{
if (previousCrcData != oggWrapper.Crc32) {
RemoveFromCrc32CacheIfExists(previousCrcData);
}
}
if (CacheOggFilesByFilename.ContainsKey(oggWrapper.Filename)) {
CacheOggFilesByFilename.Remove(oggWrapper.Filename);
}
if (CacheOggFilesByDataCRC.ContainsKey(oggWrapper.Crc32)) {
CacheOggFilesByDataCRC.Remove(oggWrapper.Crc32);
}
oggWrapper.SizeInMegabytes = ByteToMegabyte(oggWrapper.RawData.Length);
oggWrapper.IsValidSize = oggWrapper.SizeInMegabytes < Cassette.MaxCassetteFileSizeMB;
var headerTuple = GetSampleRateBitRateAndChannelCount(oggWrapper.RawData);
oggWrapper.SampleRate = headerTuple.Item1;
oggWrapper.BitRate = headerTuple.Item2;
if (!oggWrapper.IsValidSize) {
return false;
}
if (oggWrapper.SampleRate == -1 || oggWrapper.BitRate == -1) {
return false;
}
oggWrapper.AudioLength = Cassette.GetOggLength(oggWrapper.RawData);
oggWrapper.IsValidLength = oggWrapper.AudioLength > 0D && oggWrapper.AudioLength <= 36D;
if (!oggWrapper.IsValidLength) {
return false;
}
//check if that crc32 already exists in server storage - if not, skip this step
if (oggWrapper.RawData != null) {
oggWrapper.IsValid = true;
if (TapeFactory.TryGetBytesByCrc32FromServerStorage(oggWrapper.Crc32) != null) {
Instance.PrintError($"OK: CRC32 {oggWrapper.Crc32} already exists in server storage DB.");
return true;
}
uint obtainedCrc32 = TapeFactory.TryWriteBytesToServerStorage(oggWrapper.RawData);
if (obtainedCrc32 != oggWrapper.Crc32) {
Instance.PrintError($"WARNING: MISMATCH BETWEEN CRC32 OBTAINED FROM STORAGE ({obtainedCrc32}) AND PREDICTED ONE ({oggWrapper.Crc32}).DEFAULTING TO OBTAINED.");
oggWrapper.Crc32 = obtainedCrc32;
}
else {
Instance.PrintWarning($"CRC32 CHECK OK: {obtainedCrc32}");
}
}
else {
Instance.PrintError("ERROR: RAW DATA IS NULL!");
return false;
}
oggWrapper.Comments = ReadOGGComments(oggWrapper.RawData);
return true;
}
}
#endregion
#region AUDIO DIRECTORY WATCHER
public static class AudioDirectoryWatcher {
public static string AudioPath;
public static string AudioPathFilenameFormatShortWithExtension;
public static string AudioPathFilenameFormatDuplicate;
public static string AudioPathFilenameFormatShortWithExtensionDuplicate;
public static string AudioPathFilenameFormatFull;
public static string AudioPathFilenameFormatFullDuplicate;
public static FileSystemWatcher TheFSWatcher;
public static void OnServerInitialized() {
AudioPathFilenameFormatDuplicate = "{0}_{1}";
AudioPath = Interface.Oxide.DataDirectory + Path.DirectorySeparatorChar + "ogg";
if (!Directory.Exists(AudioPath)) {
Directory.CreateDirectory(AudioPath);
}
AudioPathFilenameFormatShortWithExtension = "{0}.ogg";
AudioPathFilenameFormatShortWithExtensionDuplicate = AudioPathFilenameFormatDuplicate + ".ogg";
AudioPathFilenameFormatFull = AudioPath + Path.DirectorySeparatorChar + AudioPathFilenameFormatShortWithExtension;
AudioPathFilenameFormatFullDuplicate = AudioPath + Path.DirectorySeparatorChar + AudioPathFilenameFormatShortWithExtensionDuplicate;
TheFSWatcher = new FileSystemWatcher(AudioPath);
TheFSWatcher.EnableRaisingEvents = true;
TheFSWatcher.Filter = "*.ogg";
TheFSWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.Size;
TheFSWatcher.IncludeSubdirectories = false;
TheFSWatcher.Error += AudioDirectoryWatcher_Error;
TheFSWatcher.Created += AudioDirectoryWatcher_Created;
TheFSWatcher.Changed += AudioDirectoryWatcher_Changed;
TheFSWatcher.Deleted += AudioDirectoryWatcher_Deleted;
TheFSWatcher.Renamed += AudioDirectoryWatcher_Renamed;
ProcessOGGDataDirectory();
}
public static void ProcessOGGDataDirectory() {
var oggFilesInDirectory = Directory.GetFiles(AudioPath, TheFSWatcher.Filter, SearchOption.TopDirectoryOnly);
for (var f = 0; f < oggFilesInDirectory.Length; f++) {
//will return null if the file doesn't exist, but in this case, it most certainly does
var tryWrap = OggVorbis.TryLoadNew(oggFilesInDirectory[f]);
if (tryWrap == null) {
Instance.PrintError($"ERROR PROCESSING {oggFilesInDirectory[f]}");
continue;
}
Instance.PrintError(tryWrap.ToString());
}
}
private static void AudioDirectoryWatcher_Error(object sender, ErrorEventArgs e) {
throw new System.NotImplementedException();
}
public static bool ExtensionIsExactlyOggAndNothingElse(string fullPath) {
return string.Equals(Path.GetExtension(fullPath), ".ogg", StringComparison.OrdinalIgnoreCase);
}
private static void AudioDirectoryWatcher_Created(object sender, FileSystemEventArgs e) {
if (!ExtensionIsExactlyOggAndNothingElse(e.FullPath)) {
return;
}
Instance.timer.Once(3F, () => {
if (Instance == null) {
return;
}
Instance.PrintError($">>>> CREATED FILE {e.FullPath}...");
var tryWrap = OggVorbis.TryLoadNew(e.FullPath);
if (tryWrap == null) {
Instance.PrintError($"ERROR PROCESSING {e.FullPath}");
return;
}
Instance.PrintWarning(tryWrap.ToString());
}
);
}
private static void AudioDirectoryWatcher_Changed(object sender, FileSystemEventArgs e) {
if (!ExtensionIsExactlyOggAndNothingElse(e.FullPath)) {
return;
}
Instance.PrintError($">>>> CHANGED FILE {e.FullPath}...");
var tryWrap = OggVorbis.TryUpdateExisting(e.FullPath);
if (tryWrap == null) {
Instance.PrintError($"ERROR PROCESSING {e.FullPath}");
return;
}
Instance.PrintWarning(tryWrap.ToString());
}
private static void AudioDirectoryWatcher_Deleted(object sender, FileSystemEventArgs e) {
if (!ExtensionIsExactlyOggAndNothingElse(e.FullPath)) {
return;
}
Instance.timer.Once(0.8F, () => {
Instance.PrintError($">>>> DELETED FILE {e.FullPath}...");
var tryCrc32 = OggVorbis.TryForgetExisting(e.FullPath);
if (tryCrc32 == 0) {
return;
}
Instance.PrintError($"{tryCrc32}");
}
);
}
private static void AudioDirectoryWatcher_Renamed(object sender, RenamedEventArgs e) {
if (!ExtensionIsExactlyOggAndNothingElse(e.FullPath)) {
return;
}
Instance.PrintError($">>>> RENAMED FILE FROM {e.OldFullPath} TO {e.FullPath}...");
var tryWrap = OggVorbis.TryRenameExisting(e.OldFullPath, e.FullPath);
if (tryWrap == null) {
Instance.PrintError($"ERROR PROCESSING {e.FullPath}");
return;
}
Instance.PrintError(tryWrap.ToString());
}
public static void Unload() {
AudioPath = null;
AudioPathFilenameFormatFull = null;
AudioPathFilenameFormatShortWithExtension = null;
TheFSWatcher.Dispose();
TheFSWatcher = null;
}
}
#endregion
#region TAPE FACTORY
public static class TapeFactory {
public const string FORMAT_DUMP_FILENAME = "rec_{0}.{1}.{2}_{3}_{4}_{5}";
public const string ITEM_SHORTNAME_TAPE_SHORT = "cassette.short";
public const string ITEM_SHORTNAME_TAPE_MEDIUM = "cassette.medium";
public const string ITEM_SHORTNAME_TAPE_LONG = "cassette";
public static ItemDefinition TapeDefinitionShort;
public static ItemDefinition TapeDefinitionMedium;
public static ItemDefinition TapeDefinitionLong;
public static string LastError;
public static string LastWarning;
public static Dictionary<Item, Cassette> CacheTapeToCassette;
public static string SaveBytesAsOGG(byte[] bytes, string filenameWithoutExtension) {
//the directory should exist - if it doesn't, it will be created
//if the file exists, some random
if (!Directory.Exists(AudioDirectoryWatcher.AudioPath)) {
Directory.CreateDirectory(AudioDirectoryWatcher.AudioPath);
}
if (File.Exists(string.Format(AudioDirectoryWatcher.AudioPathFilenameFormatFull, filenameWithoutExtension))) {
LastWarning = "File already exists at that location, appending random characters to the new name";
filenameWithoutExtension = string.Format(AudioDirectoryWatcher.AudioPathFilenameFormatDuplicate, filenameWithoutExtension, Path.GetRandomFileName());
}
return filenameWithoutExtension;
}
public static void OnServerInitialized() {
CacheTapeToCassette = new Dictionary<Item, Cassette>();
TapeDefinitionShort = ItemManager.FindItemDefinition(ITEM_SHORTNAME_TAPE_SHORT);
TapeDefinitionMedium = ItemManager.FindItemDefinition(ITEM_SHORTNAME_TAPE_MEDIUM);
TapeDefinitionLong = ItemManager.FindItemDefinition(ITEM_SHORTNAME_TAPE_LONG);
}
public static void Unload() {
TapeDefinitionShort = null;
TapeDefinitionMedium = null;
TapeDefinitionLong = null;
CacheTapeToCassette = null;
LastError = null;
LastWarning = null;
}
public static bool IsValidTape(Item item) {
if (item == null) {
LastError = $"Invalid tape Item: Item is NULL";
return false;
}
if (!item.info.shortname.Contains(ITEM_SHORTNAME_TAPE_LONG)) {
LastError = $"Invalid tape Item: expected one of the three Cassettes, got {item.info.displayName.translated} instead";
return false;
}
if (item.instanceData == null) {
LastError = "Invalid tape Item: instanceData is NULL";
return false;
}
if (item.instanceData.subEntity == default(NetworkableId)) {
LastError = "Invalid tape Item: subEntity reference is 0";
return false;
}
return true;
}
public static Cassette TryGetCassetteFromTape(Item tapeItem) {
if (!IsValidTape(tapeItem)) {
return null;
}
Cassette tryCassette;
if (!CacheTapeToCassette.TryGetValue(tapeItem, out tryCassette)) {
tryCassette = BaseNetworkable.serverEntities.Find(tapeItem.instanceData.subEntity) as Cassette;
//whether it's null or not, now we have it in the cache
CacheTapeToCassette.Add(tapeItem, tryCassette);
}
if (tryCassette == null) {
LastError = "Tape has a null Cassette";
}
return tryCassette;
}
public static Item MakeBlankTape(ItemDefinition useThisDefinition, ulong skinID = 0) {
if (useThisDefinition == null) {
LastError = "Item Definition is null";
return null;
}
var newItem = ItemManager.Create(useThisDefinition, 1, skinID);
if (newItem == null) {
LastError = "Item Manager produced null Item";
return null;
}
return newItem;
}
public static Item TryCloneTape(Item originalTape, bool markCloneInName = true) {
LastError = string.Empty;
LastWarning = string.Empty;
if (!IsValidTape(originalTape)) {
return null;
}
var originalCassette = TryGetCassetteFromTape(originalTape);
if (originalCassette == null) {
return null;
}
var clonedTape = MakeBlankTape(originalTape.info, originalTape.skin);
if (clonedTape == null) {
return null;
}
if (markCloneInName) {
clonedTape.text = originalTape.text + " (COPY)";
}
else {
clonedTape.text = originalTape.text;
}
if (!TryApplyContentsToCassette(originalCassette.AudioId, clonedTape, originalCassette.CreatorSteamId, originalCassette.skinID)) {
clonedTape.Remove();
return null;
}
return clonedTape;
}
public static Item ProduceTapeWithKnownID(uint crc32, ulong userID, ulong skinID, string text) {
var newTapeItem = MakeBlankTape(TapeDefinitionLong, skinID);
if (!TryApplyContentsToCassette(crc32, newTapeItem, userID, skinID, text)) {
newTapeItem.Remove();
return null;
}
return newTapeItem;
}
public static Item TryProduceTape(OggVorbis.OggWrapper oggWrapper, bool forceLongTape = false, ulong skinID = 0, ulong userID = 0) {
ItemDefinition useThisDefinition = TapeDefinitionLong;
if (!forceLongTape) {
if (oggWrapper.AudioLength <= 12D) {
useThisDefinition = TapeDefinitionShort;
}
else {
if (oggWrapper.AudioLength <= 24D) {
useThisDefinition = TapeDefinitionMedium;
}
}
}
var newTapeItem = MakeBlankTape(useThisDefinition, skinID);
if (!TryApplyContentsToCassette(oggWrapper.Crc32, newTapeItem, userID, skinID, oggWrapper.Filename)) {
newTapeItem.Remove();
return null;
}
return newTapeItem;
}
public static bool TryApplyContentsToCassette(uint crc32, Item tape, ulong userID = 0, ulong skinID = 0, string text = default(string)) {
var tryCassette = TryGetCassetteFromTape(tape);
if (tryCassette == null) {
return false;
}
tryCassette.SetAudioId(crc32, userID);
if (skinID != 0) {
if (tryCassette.skinID != skinID) {
tryCassette.skinID = skinID;
}
}
if (text != default(string)) {
tape.text = text.Replace("_", " ");
}
tape.MarkDirty();
tryCassette.SendNetworkUpdateImmediate();
return true;
}
public static byte[] TryGetBytesByCrc32FromServerStorage(uint crc32) {
try {
ulong communityEntityUlong = CommunityEntityInstanceNetID;
NetworkableId communityEntityId = new NetworkableId(communityEntityUlong);
return FileStorage.server.Get(crc32, FileStorage.Type.ogg, communityEntityId, 0);
}
catch (Exception ex) {
Console.WriteLine($"Ошибка при попытке получить данные из серверного хранилища: {ex.Message}");
return null;
}
}
public static uint TryWriteBytesToServerStorage(byte[] bytes) {
try {
ulong communityEntityUlong = CommunityEntityInstanceNetID;
NetworkableId communityEntityId = new NetworkableId(communityEntityUlong);
return FileStorage.server.Store(bytes, FileStorage.Type.ogg, communityEntityId, 0);
}
catch (Exception ex) {
Console.WriteLine($"Ошибка при попытке записать данные в серверное хранилище: {ex.Message}");
return 0;
}
}
public static string TryDumpCassetteContentsToOGG(Item tape, string filename = default(string)) {
LastError = string.Empty;
LastWarning = string.Empty;
if (!IsValidTape(tape)) {
return null;
}
var cassette = TryGetCassetteFromTape(tape);
if (cassette == null) {
return null;
}
if (cassette.AudioId == default(uint)) {
LastError = "Cassette is blank, nothing to dump!";
return null;
}
if (filename == default(string)) {
var timestamp = DateTime.Now;
filename = string.Format(FORMAT_DUMP_FILENAME, timestamp.Day, timestamp.Month, timestamp.Year, timestamp.Hour, timestamp.Minute, timestamp.Second);
}
var oggData = TryGetBytesByCrc32FromServerStorage(cassette.AudioId);
if (oggData == null) {
LastError = "Data not found in SQLite storage!";
return null;
}
SaveBytesAsOGG(oggData, filename);
return filename;
}
}
#endregion
#region WEB DOWNLOADER
public static class WebDownloader {
public enum ExistingFileHandling {
LeaveExisting, OverwriteExisting, WriteUnderNewName
}
public static Coroutine LastDownloadCoroutine;
public static bool IsURLValid(string url) {
return Uri.IsWellFormedUriString(url, UriKind.Absolute);
}
public static void OnServerInitialized() {
RequestQueue = new Queue<OggDownloadRequest>();
LastDownloadCoroutine = null;
}
public static void Unload() {
if (LastDownloadCoroutine != null) {
CommunityEntity.ServerInstance.StopCoroutine(LastDownloadCoroutine);
}
RequestQueue = null;
}
public static Queue<OggDownloadRequest> RequestQueue;
public class OggDownloadRequest : IDisposable {
public string SourceURL = string.Empty;
public IPlayer TargetIPlayer = null;
public Item TargetTapeItem = null;
public Action<bool, string, string> ActionOnDone = null;
public string TargetFilenameWithoutExtension = string.Empty;
public string TargetPath = string.Empty;
public byte[] DataBytes = null;
public void Dispose() {
DataBytes = null;
}
}
public static void TryEnqueue(string sourceURL, ExistingFileHandling existingFileHandling, IPlayer targetIPlayer = null, Item targetTapeItem = null, Action<bool, string, string> actionOnDone = null) {
Instance.PrintWarning($"TRYING TO ENQUEUE {sourceURL}...");
string targetFilenameWithoutExtension = Path.GetFileNameWithoutExtension(sourceURL);
for (var i = 0; i < RequestQueue.Count; i++) {
var currentRequest = RequestQueue.ElementAt(i);
if (currentRequest.TargetFilenameWithoutExtension == targetFilenameWithoutExtension) {
var resultMessage = $"INFO: File \"{targetFilenameWithoutExtension}\" is already in the download queue, please wait till it finishes downloading";
if (actionOnDone != null) {
actionOnDone(false, sourceURL, resultMessage);
}
if (targetIPlayer != null) {
targetIPlayer.Message(resultMessage);
return;
}
}
}
//check if that file exists...string targetPath = string.Format(AudioDirectoryWatcher.AudioPathFilenameFormatFull, targetFilenameWithoutExtension);
if (File.Exists(targetPath)) {
switch (existingFileHandling) {
case ExistingFileHandling.LeaveExisting : {
var resultMessage = $"OK: File \"{targetFilenameWithoutExtension}\" already exists and it was requested to leave it alone.";
if (actionOnDone != null) {
actionOnDone(true, sourceURL, resultMessage);
}
if (targetIPlayer != null) {
targetIPlayer.Message(resultMessage);
return;
}
return;
}
case ExistingFileHandling.OverwriteExisting : {
if (targetIPlayer != null) {
targetIPlayer.Message($"INFO: File \"{targetFilenameWithoutExtension}\" already exists, it's now going to be overwritten.");
}
break;
}
case ExistingFileHandling.WriteUnderNewName : {
var oldFileNameWithoutExtension = targetFilenameWithoutExtension;
targetFilenameWithoutExtension = string.Format(AudioDirectoryWatcher.AudioPathFilenameFormatDuplicate, targetFilenameWithoutExtension, Path.GetRandomFileName());
targetPath = string.Format(AudioDirectoryWatcher.AudioPathFilenameFormatFull, targetFilenameWithoutExtension);
if (targetIPlayer != null) {
targetIPlayer.Message($"INFO: File \"{oldFileNameWithoutExtension}\" already exists, the duplicate is going to be downloaded as {targetFilenameWithoutExtension}");
}
break;
}
}
}
//now enqueue a new request
RequestQueue.Enqueue(new OggDownloadRequest {
SourceURL = sourceURL, TargetFilenameWithoutExtension = targetFilenameWithoutExtension, TargetPath = targetPath, TargetIPlayer = targetIPlayer, TargetTapeItem = targetTapeItem, ActionOnDone = actionOnDone,
}
);
ProcessNextQueueElementIfAnything();
}
public static void ProcessNextQueueElementIfAnything() {
if (LastDownloadCoroutine != null) {
Instance.PrintWarning("ALREADY RUNNING, NOT STARTING ANOTHER COROUTINE!");
//already running, return
return;
}
if (RequestQueue.Count == 0) {
//nothing to process, return
Instance.PrintWarning("NOTHING TO PROCESS, TERMINATING!");
return;
}
var dequeued = RequestQueue.Dequeue();
LastDownloadCoroutine = CommunityEntity.ServerInstance.StartCoroutine(DownloadRequestCoroutine(dequeued));
}
public static void DownloadRequestFinalize(OggDownloadRequest downloadRequestToDispose, string myFinalMessageGoodbye, bool success = false) {
Instance.PrintError("FINALIZING DOWNLOAD REQUEST...");
if (downloadRequestToDispose.TargetIPlayer != null) {
// ... [Code preview truncated due to length. Full source in attached file] ...