示例#1
0
        public override SoundboxNode CompareCopy()
        {
            var other = new SoundboxDirectory();

            CompareCopyFill(other);
            return(other);
        }
示例#2
0
 /// <summary>
 /// Returns the given directory with its content from our database. Pass null to get the root directory.
 /// </summary>
 /// <param name="directory"></param>
 /// <returns></returns>
 //TODO we've got a bit of a race condition here: internal access is synchronized (mostly, see usage of GetCleanFile).
 // however we can get into trouble when returning files either via public getters or via client events: files can change after they have been returned here, so while the serializer is running.
 // to fully fix this we'd have to completely copy the entire sound tree whenever something changes. returning copies only would mostly work; we'd have to make sure that internally everything is synchronized
 public Task <SoundboxDirectory> GetDirectory(SoundboxDirectory directory = null)
 {
     if (directory == null)
     {
         return(Task.FromResult(GetRootDirectory()));
     }
     return(Task.FromResult(GetCleanFile(directory)));
 }
示例#3
0
 /// <summary>
 /// Sets the given watermark on a directory and all of its ancestors. This is required whenever this directory or its content is changed.
 /// </summary>
 /// <param name="directory"></param>
 /// <param name="watermark"></param>
 protected void SetWatermark(SoundboxDirectory directory, Guid watermark)
 {
     do
     {
         directory.Watermark = watermark;
         //TODO async
         Database.Update(directory);
         directory = directory.ParentDirectory;
     } while (directory != null);
 }
示例#4
0
 /// <summary>
 ///
 /// </summary>
 /// <param name="status"></param>
 /// <param name="file"></param>
 /// <param name="previousWatermark"></param>
 /// <param name="fromDirectory">
 /// See <see cref="SoundboxFileMoveEvent.FromDirectory"/>
 /// </param>
 public FileResult(ResultStatus status, SoundboxNode file, Guid?previousWatermark, SoundboxDirectory fromDirectory = null) : base(status)
 {
     if (file != null)
     {
         File = file.Flatten(true);
     }
     PreviousWatermark = previousWatermark;
     if (fromDirectory != null)
     {
         FromDirectory = fromDirectory.Flatten() as SoundboxDirectory;
     }
 }
示例#5
0
 /// <summary>
 /// Recursively adds the directory, all nodes in the directory and its descendant nodes to <see cref="NodesCache"/>.
 /// </summary>
 /// <param name="directory"></param>
 protected void BuildNodeCache(SoundboxDirectory directory)
 {
     NodesCache[directory.ID] = directory;
     foreach (var child in directory.Children)
     {
         NodesCache[child.ID] = child;
         if (child is SoundboxDirectory childDirectory)
         {
             BuildNodeCache(childDirectory);
         }
     }
 }
示例#6
0
        /// <summary>
        /// Creates a new directory in the given parent directory.
        /// </summary>
        /// <param name="directory">
        /// Information used when adding the new sound:<list type="bullet">
        /// <item><see cref="SoundboxNode.Name"/></item>
        /// <item><see cref="SoundboxNode.Tags"/></item>
        /// </list>
        /// </param>
        /// <param name="parent">
        /// Null: root directory is assumed.
        /// </param>
        /// <returns></returns>
        public FileResult MakeDirectory(SoundboxDirectory directory, SoundboxDirectory parent)
        {
            if (directory == null)
            {
                //error
                return(new FileResult(BaseResultStatus.INVALID_PARAMETER));
            }
            if (!CheckUploadDisplayName(directory.Name))
            {
                //error
                return(new FileResult(FileResultStatus.INVALID_FILE_NAME));
            }

            if (parent == null)
            {
                parent = GetRootDirectory();
            }
            else
            {
                parent = GetCleanFile(parent);
                if (parent == null)
                {
                    //given parent does not exist => error
                    return(new FileResult(FileResultStatus.FILE_DOES_NOT_EXIST));
                }
            }

            //TODO check display name unique in parent

            directory.ID              = Guid.NewGuid();
            directory.Children        = new List <SoundboxNode>();
            directory.ParentDirectory = parent;

            ResultStatus status = BaseResultStatus.INTERNAL_SERVER_ERROR;

            try
            {
                DatabaseLock.EnterWriteLock();

                return(UploadOnNewFile(directory, parent));
            }
            catch (Exception ex)
            {
                Log(ex);
            }
            finally
            {
                DatabaseLock.ExitWriteLock();
            }

            return(new FileResult(status));
        }
示例#7
0
        /// <summary>
        /// Loads the root directory and thuse the entire file tree from our persistent database.
        /// If the database is empty then a newly created empty directory is returned.
        /// </summary>
        /// <returns></returns>
        protected async Task <SoundboxDirectory> LoadRoot()
        {
            var root = await Database.Get();

            if (root == null)
            {
                //database is empty => create new root directory
                root           = new SoundboxDirectory();
                root.ID        = Guid.NewGuid();
                root.Watermark = Guid.NewGuid();

                await Database.Insert(root);
            }

            return(root);
        }
示例#8
0
        /// <summary>
        /// Returns the given directory's children (i.e. fetches <see cref="SoundboxDirectory.Children"/>).
        /// </summary>
        /// <param name="directory">The directory content to fetch. Null for base directory</param>
        /// <param name="recursive">Whether to recursively fetch the entire file branch.</param>
        /// <returns>
        /// The directory's content. If null is passed for the <paramref name="directory"/> (requesting the root directory) and <paramref name="recursive"/> is false
        /// then only the root directory is returned without its children. That can be utilized to quickly verify whether the local sound list is up-to-date by checking the root directory's <see cref="SoundboxDirectory.Watermark"/>.
        /// </returns>
        public async Task <ICollection <SoundboxNode> > GetSounds(SoundboxDirectory directory = null, bool recursive = false)
        {
            SoundboxDirectory serverDirectory = await GetSoundbox().GetDirectory(directory);

            if (directory == null && recursive)
            {
                //return the entire tree
                return(new SoundboxNode[]
                {
                    serverDirectory
                });
            }

            if (directory == null)
            {
                //root directory without children
                return(new SoundboxNode[]
                {
                    serverDirectory.Flatten()
                });
            }

            //return the requested directory's children
            if (serverDirectory == null)
            {
                //invalid directory has been passed
                return(null);
            }

            if (recursive)
            {
                return(serverDirectory.Children);
            }
            //else: flatten the result; do not return the children's children

            ICollection <SoundboxNode> children = new List <SoundboxNode>();

            foreach (var child in serverDirectory.Children)
            {
                children.Add(child.Flatten());
            }

            return(children);
        }
示例#9
0
        /// <summary>
        /// Recursively loads the entire directory's content.
        /// The given directory itself is loaded and modified as required (in/out parameter).
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="directory"></param>
        private void LoadDirectory(ILiteCollection <SoundboxNode> collection, SoundboxDirectory directory)
        {
            //at this point we already have the children's IDs loaded
            //still it's probably most efficient to just load the directory's children again
            var resultChildren = collection.Query()
                                 .Include(BsonExpression.Create("children"))
                                 .Where(f => f.ID == directory.ID)
                                 .Select(BsonExpression.Create("children"));

            ICollection <SoundboxNode> children = new List <SoundboxNode>();

            foreach (var childRow in resultChildren.ToEnumerable())
            {
                if (childRow == null)
                {
                    continue;
                }

                var bsonChildren = childRow["children"];
                if (bsonChildren == null)
                {
                    continue;
                }

                foreach (var bsonChild in bsonChildren.AsArray)
                {
                    var child = this.BsonMapper.Deserialize <SoundboxNode>(bsonChild);
                    //test for dangling references:
                    if (child.ID == default)
                    {
                        //not a proper file
                        continue;
                    }

                    children.Add(child);
                }
            }

            directory.Children = children;

            //continue loading
            LoadDirectoryChildren(collection, directory);
        }
示例#10
0
        /// <summary>
        /// Flattens the directory and returns a copy without <see cref="Children"/> and <see cref="SoundboxNode.ParentDirectory"/>.
        /// This is often used when returning files to a client when only a restricted set of information should be passed instead of the entire file tree.
        /// </summary>
        /// <returns></returns>
        public override SoundboxNode Flatten(bool withParent = false)
        {
            var flattened = new SoundboxDirectory()
            {
                ID              = this.ID,
                Name            = this.Name,
                IconUrl         = this.IconUrl,
                Tags            = this.Tags,
                Watermark       = this.Watermark,
                Children        = null,
                ParentDirectory = null,
                Flattened       = true
            };

            if (withParent && this.ParentDirectory != null)
            {
                flattened.ParentDirectory = this.ParentDirectory.Flatten() as SoundboxDirectory;
            }
            return(flattened);
        }
示例#11
0
        public async Task <FileResult> Post([FromQuery] string sound, [FromQuery] string directory = null)
        {
            Sound soundActual = null;

            if (!string.IsNullOrWhiteSpace(sound))
            {
                soundActual    = JsonConvert.DeserializeObject <Sound>(sound);
                soundActual.ID = SoundboxNode.ID_DEFAULT_NEW_ITEM;
            }

            SoundboxDirectory directoryActual = null;

            if (!string.IsNullOrWhiteSpace(directory))
            {
                directoryActual = JsonConvert.DeserializeObject <SoundboxDirectory>(directory);
            }

            var soundbox = HttpContext.RequestServices.GetService(typeof(Soundbox)) as Soundbox;

            return(await soundbox.UploadSound(Request.Body, soundActual, directoryActual));
        }
示例#12
0
        /// <summary>
        /// Adds the given new file to our in-memory data structures and to our database. Updates all clients on success.
        /// </summary>
        /// <param name="newFile"></param>
        /// <param name="parent"></param>
        private FileResult UploadOnNewFile(SoundboxNode newFile, SoundboxDirectory parent)
        {
            try
            {
                DatabaseLock.EnterWriteLock();

                //save previous watermark for event
                Guid previousWatermark = GetRootWatermark();
                Guid newWatermark      = Guid.NewGuid();

                //add to cache
                parent.AddChild(newFile);
                NodesCache[newFile.ID] = newFile;
                //add to database
                //TODO async
                Database.Insert(newFile);

                //update cache and database watermarks (this will call Update for parent)
                SetWatermark(newFile, newWatermark);

                //update our clients
                GetHub().OnFileEvent(new SoundboxFileChangeEvent()
                {
                    Event             = SoundboxFileChangeEvent.Type.ADDED,
                    File              = FlattenForEvent(newFile),
                    PreviousWatermark = previousWatermark
                });

                if (newFile is Sound sound)
                {
                    SpeechRecognition_OnSoundChanged(sound, null);
                }

                return(new FileResult(BaseResultStatus.OK, newFile, previousWatermark));
            }
            finally
            {
                DatabaseLock.ExitWriteLock();
            }
        }
示例#13
0
 /// <summary>
 /// Iterates through all of the directory's <see cref="SoundboxDirectory.Children"/>
 /// and loads the content of child directories via <see cref="LoadDirectory(ILiteCollection{SoundboxNode}, SoundboxDirectory)"/>.
 /// </summary>
 /// <param name="collection"></param>
 /// <param name="directory"></param>
 private void LoadDirectoryChildren(ILiteCollection <SoundboxNode> collection, SoundboxDirectory directory)
 {
     foreach (var child in directory.Children)
     {
         child.ParentDirectory = directory;
         if (child is SoundboxDirectory childDirectory)
         {
             LoadDirectory(collection, childDirectory);
         }
     }
 }
示例#14
0
        /// <summary>
        /// Moves a file to a new directory without performing an <see cref="Edit(SoundboxNode)"/>.
        /// </summary>
        /// <param name="file"></param>
        /// <param name="directory">
        /// Null: uses the root directory instead.
        /// </param>
        /// <returns></returns>
        public async Task <FileResult> Move(SoundboxNode file, SoundboxDirectory directory)
        {
            file = GetCleanFile(file);
            if (file == null)
            {
                return(new FileResult(BaseResultStatus.INVALID_PARAMETER));
            }
            if (IsRootDirectory(file))
            {
                return(new FileResult(FileResultStatus.ILLEGAL_FILE_EDIT_DENIED_ROOT));
            }

            if (directory == null)
            {
                directory = GetRootDirectory();
            }
            else
            {
                directory = GetCleanFile(directory);
                if (directory == null)
                {
                    return(new FileResult(BaseResultStatus.INVALID_PARAMETER));
                }
            }

            if (file == directory)
            {
                //not possible
                return(new FileResult(FileResultStatus.MOVE_TARGET_INVALID));
            }
            if (file.ParentDirectory == directory)
            {
                //no change
                return(new FileResult(BaseResultStatus.OK_NO_CHANGE));
            }

            try
            {
                DatabaseLock.EnterWriteLock();

                //save previous watermark for event
                Guid previousWatermark = GetRootWatermark();
                Guid newWatermark      = Guid.NewGuid();

                //move in cache
                SoundboxDirectory oldParent = file.ParentDirectory;
                oldParent.Children.Remove(file);

                directory.AddChild(file);

                //update file in database
                if (!(file is SoundboxDirectory))
                {
                    //TODO async
                    Database.Update(file);
                }
                //else: is updated anyways in SetWatermak

                //update cache and database watermarks (this will call Update for parent)
                SetWatermark(file, newWatermark);
                SetWatermark(oldParent, newWatermark);

                //update our clients
                GetHub().OnFileEvent(new SoundboxFileMoveEvent()
                {
                    Event             = SoundboxFileChangeEvent.Type.MOVED,
                    File              = FlattenForEvent(file),
                    FromDirectory     = oldParent,
                    PreviousWatermark = previousWatermark
                });

                return(new FileResult(BaseResultStatus.OK, file, previousWatermark, oldParent));
            }
            catch (Exception ex)
            {
                Log(ex);
                return(new FileResult(BaseResultStatus.INTERNAL_SERVER_ERROR));
            }
            finally
            {
                DatabaseLock.ExitWriteLock();
            }
        }
示例#15
0
        /// <summary>
        /// Uploads a new sound and adds it to the given directory.
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="sound">
        /// Information used when adding the new sound:<list type="bullet">
        /// <item><see cref="SoundboxNode.Name"/></item>
        /// <item><see cref="SoundboxFile.FileName"/></item>
        /// <item><see cref="SoundboxNode.Tags"/></item>
        /// <item><see cref="Sound.VoiceActivation"/></item>
        /// </list>
        /// </param>
        /// <param name="directory">
        /// Null: the root directory is used instead.
        /// </param>
        /// <returns></returns>
        public async Task <FileResult> UploadSound(Stream bytes, Sound sound, SoundboxDirectory directory)
        {
            if (sound == null || bytes == null)
            {
                //error
                return(new FileResult(BaseResultStatus.INVALID_PARAMETER));
            }
            if (!CheckUploadFileName(sound.FileName))
            {
                if (GetFileType(sound.FileName) != null && !CheckUploadFileType(sound.FileName))
                {
                    //file type is not supported
                    return(new FileResult(FileResultStatus.ILLEGAL_FILE_TYPE));
                }
                return(new FileResult(FileResultStatus.INVALID_FILE_NAME));
            }
            if (!CheckUploadDisplayName(sound.Name))
            {
                //error
                return(new FileResult(FileResultStatus.INVALID_FILE_NAME));
            }

            if (directory == null)
            {
                directory = GetRootDirectory();
            }
            else
            {
                directory = GetCleanFile(directory);
                if (directory == null)
                {
                    //does not exist
                    return(new FileResult(FileResultStatus.FILE_DOES_NOT_EXIST));
                }
            }

            //TODO check display name unique in parent

            //we store only a few select properties of the given sound
            Sound soundClean = new Sound();

            soundClean.ID              = Guid.NewGuid();
            soundClean.Name            = sound.Name;
            soundClean.FileName        = sound.FileName;
            soundClean.Tags            = sound.Tags;
            soundClean.VoiceActivation = sound.VoiceActivation;
            soundClean.ParentDirectory = directory;

            sound = soundClean;
            MakeSoundFileName(sound);

            ResultStatus status = BaseResultStatus.INTERNAL_SERVER_ERROR;

            //TODO: write into temp file, lock database etc, copy file, add to directory object, save database, update clients, unlock
            //write into temp file
            string tempFile = GetUploadTempFile();

            try
            {
                try
                {
                    using (var output = File.OpenWrite(tempFile))
                    {
                        await bytes.CopyToAsync(output);
                    }
                }
                catch
                {
                    status = FileResultStatus.IO_ERROR;
                    throw;
                }

                //read meta data from temp file (i.e. before we enter the database lock)
                var metaDataProvider = ServiceProvider.GetService(typeof(IMetaDataProvider)) as IMetaDataProvider;
                if (metaDataProvider != null)
                {
                    sound.MetaData = await metaDataProvider.GetMetaData(tempFile);
                }

                try
                {
                    DatabaseLock.EnterWriteLock();

                    //move the file to the target location
                    try
                    {
                        File.Move(tempFile, GetAbsoluteFileName(sound));
                    }
                    catch
                    {
                        status = FileResultStatus.IO_ERROR;
                        //delete the target file
                        try
                        {
                            File.Delete(GetAbsoluteFileName(sound));
                        }
                        catch { }

                        throw;
                    }

                    return(UploadOnNewFile(sound, directory));
                }
                finally
                {
                    DatabaseLock.ExitWriteLock();
                }
            }
            catch (Exception ex)
            {
                Log(ex);
            }
            finally
            {
                //delete the temp file should it still exist
                try
                {
                    File.Delete(tempFile);
                }
                catch { }
            }

            return(new FileResult(status));
        }
示例#16
0
 /// <summary>
 /// Creates a new directory in the given parent directory.
 /// </summary>
 /// <param name="directory">
 /// Information used when adding the new sound:<list type="bullet">
 /// <item><see cref="SoundboxNode.Name"/></item>
 /// <item><see cref="SoundboxNode.Tags"/></item>
 /// </list>
 /// </param>
 /// <param name="parent">
 /// Null: root directory is assumed.
 /// </param>
 /// <returns></returns>
 public FileResult MakeDirectory(SoundboxDirectory directory, SoundboxDirectory parent)
 {
     return(GetSoundbox().MakeDirectory(directory, parent));
 }
示例#17
0
 /// <summary>
 /// Moves a file to a new directory. There is no <see cref="Edit(SoundboxNode)"/> performed on the given file.<br/>
 /// If the given directory is null then the root directory is used.
 /// </summary>
 /// <param name="file"></param>
 /// <param name="directory"></param>
 /// <returns></returns>
 public Task <FileResult> Move(SoundboxNode file, SoundboxDirectory directory)
 {
     return(GetSoundbox().Move(file, directory));
 }