Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
August 15, 2021 11:50 pm GMT

Electron Adventures: Episode 23: Display Information about Files

Let's improve our file manager. There's a lot of information we'd like to display. Let's start with just a few:

  • file size
  • last modified time
  • for symlink, where does it lead to

preload.js

This tiny change already requires restructuring the code a bit, as getting this information in node is - obviously async.

let { readdir } = require("fs/promises")let directoryContents = async (path) => {  let results = await readdir(path, {withFileTypes: true})  return await Promise.all(results.map(entry => fileInfo(path, entry)))}

I'm not sure how node actually executes it. Pretty much every other language will run system calls one at a time, so we could do return results.map(entry => await fileInfo(path, entry)), but on an off chance that this actually runs in parallel I'm first constructing big list, then awaiting the whole thing.

Now the next part gets a bit awkward. Having a functions of a few lines in preload.js is fine, but this is getting big. We'd much rather put it into some backend code, which we can unit test without complexities of frontend testing. We will absolutely get to it soon.

let { stat, readlink } = require("fs/promises")let fileInfo = async (basePath, entry) => {  let {name} = entry  let fullPath = path.join(basePath, name)  let linkTarget = null  let fileStat  if (entry.isSymbolicLink()) {    linkTarget = await readlink(fullPath)  }  // This most commonly happens with broken symlinks  // but could also happen if the file is deleted  // while we're checking it as race condition  try {    fileStat = await stat(fullPath)  } catch {    return {      name,      type: "broken",      linkTarget,    }  }  let {size, mtime} = fileStat  if (fileStat.isDirectory()) {    return {      name,      type: "directory",      mtime,      linkTarget,    }  } else if (fileStat.isFile()) {    return {      name,      linkTarget,      type: "file",      size,      mtime,      linkTarget,    }  } else {    return {      name,      type: "special",    }  }}

This should cover a lot of cases, such as:

  • file
  • symlink to a file
  • directory
  • symlink to a directory
  • error (file deleted while we're checking it)
  • symlink to an error (most likely symlink just points to non-existent file, very common)
  • special file (socket, fifo, device, etc)
  • symlink to a special file

Sounds like something we should unit test? We absolutely will do, just not yet!

index.html

One thing I forgot about. When you're serving HTML from just about any webserver, it tells the browser it's UTF8 in HTTP headers. As we're loading raw files, browsers default to some paleolithic encoding nobody's seen since before Y2K, and even Electron does that crazy thing. So we need to tell it that it's UTF8. Here's one of many ways to do so:

<!DOCTYPE html><html>  <head>    <meta charset="UTF-8">  </head>  <body>    <link rel="stylesheet" href="/build/bundle.css">    <script src="/build/bundle.js"></script>  </body></html>

App.svelte

And here's some very simple component for displaying that information in a grid format - name, type, size, last modified time. We can do a lot better, and we absolutely will.

<script>  let directory = window.api.currentDirectory()  $: filesPromise = window.api.directoryContents(directory)  $: isRoot = (directory === "/")  function navigate(path) {    if (directory === "/") {      directory = "/" + path    } else {      directory += "/" + path    }  }  function navigateUp() {    directory = directory.split("/").slice(0, -1).join("/") || "/"  }  function formatDate(d) {    return d ? d.toDateString() : ""  }  function formatName(entry) {    if (entry.linkTarget) {      return `${entry.name}  ${entry.linkTarget}`    } else {      return entry.name    }  }</script><h1>{directory}</h1>{#await filesPromise}{:then files}  <div class="file-list">    {#if !isRoot}      <div><button on:click={() => navigateUp()}>..</button></div>      <div></div>      <div></div>      <div></div>    {/if}    {#each files as entry}      <div>        {#if entry.type === "directory"}          <button on:click={() => navigate(entry.name)}>            {formatName(entry)}          </button>        {:else}          {formatName(entry)}        {/if}      </div>      <div>        {entry.type}        {entry.linkTarget ? " link" : ""}      </div>      <div>{entry.size ? entry.size : ""}</div>      <div>{formatDate(entry.mtime)}</div>    {/each}  </div>{/await}<style>  :global(body) {    background-color: #444;    color: #ccc;  }  .file-list {    display: grid;    grid-template-columns: 3fr 1fr 1fr 1fr;  }</style>

Results

Here's the results, for root directory, and some directory in node_modules:

Episode 23 Screenshot A

Episode 23 Screenshot B

In the next episode, we'll extract some of that backend code into something we can unit test.

As usual, all the code for the episode is here.


Original Link: https://dev.to/taw/electron-adventures-episode-23-display-information-about-files-2656

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To