Home > Software engineering >  How to make a class that holds two type of files, so they are linked to each other?
How to make a class that holds two type of files, so they are linked to each other?

Time:11-22

I'm trying to make a table that lists 2 types of files linked together. One type are .mp3, and the other are .txt files. I want these files to be linked together, such that the files that share the same name share one row, when the foreach loop passes through them. This is so that the mp3 files can be played, and the corresponding text file can be opened.

App.razor page has a table that displays all files in a folder, but it doesn't take into account if the files of the 2 types share the same name. Can anybody help with how to make a class that has the files linked together so that they can be called in the table?

Here is the code.

<table >
    <thead>
    <tr>
        <th scope="col">Name</th>
        <th scope="col">Actions</th>
    </tr>
    </thead>
    <tbody>
        @foreach (var fileGroup in myFilesGroupedAndSorted)
        {
            <Mp3 FileGroup=fileGroup />
        }
    </tbody>
</table>


@code {
    readonly List<TextFile> textList = new();
    private string audioUrl { get; set; }
    readonly string audioFolderName = "textFiles";

    protected override void OnInitialized()
    {
        var path = $"{env.WebRootPath}\\{audioFolderName}\\"; //System.IO.Path.ChangeExtension(@"wwwroot\textFiles\", null); 
        var files = new DirectoryInfo(path).GetFiles();

        foreach (var file in files)
        {
            textList.Add(new TextFile
            {
                Name = file.Name,
                Url = $"/textFiles/{file.Name}",
                Path = file.FullName
            });
        }
    }

    IEnumerable<IGrouping<string, TextFile>> myFilesGroupedAndSorted
            => textList.GroupBy(file => GetPathWithoutExtension(file.Path))
                     .OrderBy(group => group.Key);

    private string GetPathWithoutExtension(string path)
    {
        return System.IO.Path.ChangeExtension(path, null);
    }
}

Mp3.razor

<tr>
    <td>
        @FileGroup.Key
    </td>
    <td>
        @if (Mp3file is not null)
        {
            <span @onclick="() => PlayAudio(Mp3file.Url)"
               aria-hidden="true" role="button">
            </span>
            
        }
        @if (Text is not null)
        {
            <span @onclick="() => openTextFile(Text)">
                <button>Open</button>
            </span>
        }
    </td>
</tr>
@code {
    readonly List<TextFile> textList = new();
    private string audioUrl { get; set; }

    [Parameter]
    public IGrouping<string, TextFile> FileGroup { get; set; } = default!;

    TextFile? Text => FileGroup.FirstOrDefault(file => Path.GetExtension(file.Path).ToLower() == "txt");
    TextFile? Mp3file => FileGroup.FirstOrDefault(file => Path.GetExtension(file.Path).ToLower() == "mp3");

    private void PlayAudio(string url)
    {
        audioUrl = url;
        InvokeAsync(StateHasChanged);
    }

    private async Task DeleteAudio(TextFile text)
    {
        ...
    }

    public void openTextFile(TextFile text)
    {
        ...
    }
}

CodePudding user response:

You need to "pivot" the table, i.e. convert a list to a table. LINQ can be used for this. Check out this answer. Instead of "Jan" and "Feb" you would have properties bool HasMp3 and bool HasTxt, with appropriate Where clauses. Something like this:

var files = new DirectoryInfo(path).GetFiles();

textList.AddRange(files
    .GroupBy(c => Path.ChangeExtension(c.Name, null))
    .Select(g => new TextFile() {
        Name = g.Key,
        Url = $"/textFiles/{g.Key}",
        HasMp3 = g.Any(c => Path.GetExtension(c) == ".mp3"),
        HasTxt = g.Any(c => Path.GetExtension(c) == ".txt"),
    }));

With these two bool properties you can hide buttons if corresponding file does not exist. Url property does not have an extension. You would have to append every appropriate extension in every appropriate function.

CodePudding user response:

Use Linq's GroupBy

@foreach(var fileGroup in myFilesGroupedAndSorted)
{
    <h3>@fileGroup.Key</h3>
    @foreach(var file in fileGroup.OrderBy( file => file.Path))
    {
        <div>@file.Path</div>
    }
}

@code {
    // without access to your folder I generated random data.
    List<TextFile> myList = new List<TextFile>
    {
        new TextFile { Name = "", Path = "some-path\\SomeFile1.txt", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile2.txt", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile3.txt", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile4.txt", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile5.txt", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile6.txt", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile1.mp3", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile2.mp3", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile3.mp3", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile4.mp3", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile5.mp3", Url = "" },
        new TextFile { Name = "", Path = "some-path\\SomeFile6.mp3", Url = "" },
    };


    IEnumerable<IGrouping<string, TextFile>> myFilesGroupedAndSorted
        => myList.GroupBy(file => GetPathWithoutExtension(file.Path))
                 .OrderBy(group => group.Key);

    private string GetPathWithoutExtension(string path)
    {
        return System.IO.Path.ChangeExtension(path, null);
    }
}

Processing each group then should be trivial by testing for each file extension. I would make a sub-component to pass the IGrouping<string, TextFile> as a parameter to simplify the logic...

Mp3.Razor

<tr>
    <td>
        @FileGroup.Key
    </td>
    <td>
        @if (Mp3 is not null)
        {
            <span @onclick="() => PlayAudio(Mp3.Url)"
               aria-hidden="true" role="button">
            </span>
            <span @onclick="() => DeleteAudio(FileGroup.Key)"
               aria-hidden="true" role="button">
            </span>
        }
        @if (Text is not null)
        {
            <span @onclick="() => openTextFile(Text)">
                <button>Open</button>
            </span>
        }
    </td>
</tr>
@code {
    [Parameter]
    public IGrouping<string, TextFile> FileGroup { get; set; } = default!;

    TextFile? Text => FileGroup.FirstOrDefault(file => Path.GetExtension(file.Path).ToLower() == "txt");
    TextFile? Mp3 => FileGroup.FirstOrDefault(file => Path.GetExtension(file.Path).ToLower() == "mp3");
}
...
  <tbody>
    @foreach(var fileGroup in myFilesGroupedAndSorted)
    {
       <Mp3 FileGroup=fileGroup /> 
    }
  </tbody>
...
  • Related