Home > Enterprise >  Detect in which folder a file should be uploaded
Detect in which folder a file should be uploaded

Time:01-22

I would like some help to adjust an upload script, my goal is for the script to detect in which folder a file should be uploaded.

I created an HTML enter image description here

This form has 2 files, the .gs file has this code here:

function doGet(e) {
  return HtmlService.createTemplateFromFile('forms0101.html').evaluate();
}


function getOAuthToken() {
  return ScriptApp.getOAuthToken();
}


function getParent(){
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var id = ss.getId();
  var parent = DriveApp.getFileById(id).getParents().next().getId();
  return parent
}

function getLimitFolder(){
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var pastapai = DriveApp.getFileById(ss.getId()).getParents();
  var limitfolder = pastapai.next().getFoldersByName("_").next().getId();
  return limitfolder
}

const saveDataAsCSV = (data, folderId) => DriveApp.getFolderById(folderId).createFile("sample.csv", data);

/**
* creates a folder under a parent folder, and returns it's id. If the folder already exists
* then it is not created and it simply returns the id of the existing one
*/
function createOrGetFolder(folderName, parentFolderId) {
  try {
    var parentFolder = DriveApp.getFolderById(parentFolderId),
      folder;

    if (parentFolder) {
      var foldersIter = parentFolder.getFoldersByName("Video");
      if (foldersIter.hasNext()) {
        video_folder = foldersIter.next().getFoldersByName(folderName); //folderName esta definido no arquivo forms.html
        if (video_folder.hasNext()) {
        folder = video_folder.next();
        }
      } else {
        folder = parentFolder.createFolder("Video");
        folder = folder.createFolder(folderName);
      }
    } else {
      throw new Error("Parent Folder with id: "   parentFolderId   " not found");
    }

    return folder.getId();
  } catch (error) {
    return error;
  }
}

// NOTE: always make sure we use DriveApp, even if it's in a comment, for google to import those
// libraries and allow the rest of the app to work. see https://github.com/tanaikech/Resumable_Upload_For_WebApps

The HTML file has exactly this code here:

<!DOCTYPE html>
<html>
  <head>
    <base target="_blank">
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Drive Multi Large File Upload</title>
    <link href="https://fonts.googleapis.com/icon?family=Material Icons" rel="stylesheet">

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
     
     <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA 058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <style>
      @import url('https://fonts.googleapis.com/css2?family=Rubik:wght@400;600;700&display=swap');
      
      .disclaimer{
        width: 480px; 
        color: #646464;
        margin: 20px auto;
        padding:0 16px;
        text-align:center;
        font:400 12px Rubik,sans-serif;
        }

      h5.center-align.teal-text {
        font:700 26px Rubik,sans-serif;
        color: #00F498!important;
      }

      .row {
        font:600 14px Rubik,sans-serif;
      }

      .btn {
        background-color: black;
      }

      .btn:hover {
        background-color: #00F498;
      }

      body {
        margin-top: -40px;
      }

      #progress {
        color: #00000;
      }
      
      .disclaimer a{
        color: #00BCAA;
        }

      #credit{
        display:none
        }
    </style>
  </head>
  <body>
    <form  id="form" novalidate="novalidate" style="max-width: 480px;margin: 40px auto;">
 <div id="forminner">
  <h5  style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">YOUR NAME</h5>
    <div >
          <div >
            <input id="name01" type="text" name="Name"  required="required" aria-required="true">
            <label for="name" >Name</label>
          </div>
        </div>
    
      <h5  style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">SOME DESCRIPTION</h5>
        <div >
          <div >
            <input id="description" type="text" name="Description"  required="required" aria-required="true">
            <label for="name">Description</label>
          </div>
        </div>

    <div >
      <div >
        <h6>Model</h6>       
        <select  id="Model">
          <option selected="">Choose...</option>
          <option value="01">01</option>
          <option value="02">02</option>
          <option value="03">03</option>
        </select> 
          &nbsp;

        <h6>Color</h6>        
        <select  id="Color">
          <option selected="">Choose...</option>
          <option value="Red">Red</option>
          <option value="Green">Green</option>
        </select>   
      </div>
  </div>

    
        <div >
          <div >
            <h5 >Upload the Video File</h5>
           
          </div>
        </div>
        
        <div >
          <div >
            <div id="input-btn" >
              <span>File</span>
              <input id="files" type="file" single="">
            </div>
            <div >
              <input  type="text" placeholder="Select the file">
            </div>
          </div>
        </div>

        <div >
          <div >
            <button id="submit-btn"  type="submit" onclick="submitForm(); return false;">Submit</button>
          </div>
        </div>
        <div >
          <div  id="update">
            <hr>
            <p>
              Por favor, aguarde enquanto seu arquivo está sendo carregado.<br><span style="color: #00000;"><b>Não feche ou atualize a janela durante o upload.</b></span>
            </p>
          </div>
        </div>
        <div >
          <div  id="progress">
          </div>
        </div>
      </div>
     </div> 
      <div id="success" style="display:none">
        <h5 >Tudo certo!</h5>
        <p>Se você já preencheu todos os campos é só fechar essa janela e clicar em enviar!</p>
        <button id="fechar"  style ="transform: translateX(160%);" type="button" onclick="google.script.host.close()">Fechar</button>
      </div>
    </form>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
    <script src="https://gumroad.com/js/gumroad.js"></script>

    <script>

      var upload_folder = "01-01"
      const chunkSize = 5242880;
      const uploadParentFolderId = <?=getParent()?>; // creates a folder inside of this folder
      const limitfolder = <?=getLimitFolder()?>;

      function closer(){
        google.script.host.close();
      }

      function submitForm() {

        // Added the below script.
        if ($('#submit-btn.disabled')[0]) return; // short circuit

        var name = upload_folder
        var files = [...$('#files')[0].files]; // convert from FileList to array

        
        if (files.length === 0) {
          showError("Por favor, selecione um arquivo");
          return;
        }

        var name = $('#name01').val();
        var description = $('#description').val();
        var model = $('#Model').val();
        var color = $('#Color').val();
        var form_values = [name, description, model, color];
        form_values = form_values.map(r => r.replaceAll(",", "#")); // Essa linha substitui todas as "," por "#" antes de gerar o .csv
        var data = form_values.join(",");
        google.script.run.saveDataAsCSV(data, uploadParentFolderId);
        google.script.run.saveDataAsCSV(data, limitfolder);

        disableForm(); // prevent re submission

        // the map and reduce are here to ensure that only one file is uploaded at a time. This allows
        // the promises to be run sequentially
        files.map(file => uploadFilePromiseFactory(file))
            .reduce((promiseChain, currentTask) => {
              return promiseChain.then(currentTask);
            }, Promise.resolve([])).then( () => {
              console.log("Completed all files upload");
              showSuccess();
            });
      }

      function disableForm() {
        $('#submit-btn').addClass('disabled');
        $('#input-btn').addClass('disabled');
        $('#update').removeClass('hide');
         $('#update').removeClass('hide');
      }

      function uploadFilePromiseFactory(file) {
        return () => {
          console.log("Processing: ", file.name);
          return new Promise((resolve, reject) => {

            showProgressMessage("Seu arquivo está sendo carregado");

            var fr = new FileReader();
            fr.fileName = file.name;
            fr.fileSize = file.size;
            fr.fileType = file.type;
            // not sure of a better way of passing the promise functions down
            fr.resolve = () => resolve();
            fr.reject = (error) => reject(error);
            fr.onload = onFileReaderLoad;
            fr.readAsArrayBuffer(file);
          });
        };
      }
      
      /**
       * Gets called once the browser has loaded a file. The main logic that creates a folder
       * and initiates the file upload resides here
       */
      function onFileReaderLoad(onLoadEvent) {
        var fr = this;

        var newFolderName = upload_folder
        createOrGetFolder(newFolderName, uploadParentFolderId).then(newFolderId => {
          console.log("Found or created guest folder with id: ", newFolderId);
          uploadFileToDriveFolder.call(fr, newFolderId).then(() => {
              fr.resolve();
            }, (error) => {
              fr.reject(error);
            });
          },
          (error) => {
            if (error) {
              showError(error.toString());
            }
            console.log("onFileReaderLoad Error2: ", error);
          });

      }

      /**
       * call to the DriveApp api. Wrapped in a promise in case I want to address timing issues between a
       * createFolder and findFolderById
       */
      function createOrGetFolder(folderName, parentFolderId) {
        return new Promise((resolve, reject) => {
          google.script.run.withSuccessHandler(response => {
            console.log("createOrGetFolder response: ", response);
            if (response && response.length) {
              resolve(response);
            }
            reject(response);
          }).createOrGetFolder(folderName, parentFolderId);
        });
      }

      /**
      * Helper functions modified from:
      * https://github.com/tanaikech/Resumable_Upload_For_WebApps
      */
      function uploadFileToDriveFolder(parentFolderId) {
        var fr = this;
        return new Promise((resolve, reject) => {
          var fileName = fr.fileName;
          var fileSize = fr.fileSize;
          var fileType = fr.fileType;
          console.log({fileName: fileName, fileSize: fileSize, fileType: fileType});
          var buf = fr.result;
          var chunkpot = getChunkpot(chunkSize, fileSize);
          var uint8Array = new Uint8Array(buf);
          var chunks = chunkpot.chunks.map(function(e) {
            return {
               data: uint8Array.slice(e.startByte, e.endByte   1),
               length: e.numByte,
               range: "bytes "   e.startByte   "-"   e.endByte   "/"   chunkpot.total,
            };
          });
          google.script.run.withSuccessHandler(oAuthToken => {
            var xhr = new XMLHttpRequest();
            xhr.open("POST", "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");
            xhr.setRequestHeader('Authorization', "Bearer "   oAuthToken);
            xhr.setRequestHeader('Content-Type', "application/json");
            xhr.send(JSON.stringify({
              mimeType: fileType,
              name: fileName,
              parents: [parentFolderId]
            }));
            xhr.onload = () => {
              doUpload(fileName, {
                location: xhr.getResponseHeader("location"),
                chunks: chunks,
              }).then(success => {
                resolve(success);
                console.log("Successfully uploaded: ", fileName);
              },
              error => {
                reject(error);
              });
            };

            xhr.onerror = () => {
              console.log("ERROR: ", xhr.response);
              reject(xhr.response);
            };
          }).getOAuthToken();
        });
      }

      function showSuccess() {
        $('#forminner').hide();
        $('#success').show();
        $('#fechar').show();
      }

      function showError(e) {
        $('#progress').addClass('red-text').html(e);
      }

      function showMessage(e) {
        $('#update').html(e);
      }

      function showProgressMessage(e) {
        $('#progress').removeClass('red-text').html(e);
      }

      /**
      * Helper functions modified from:
      * https://github.com/tanaikech/Resumable_Upload_For_WebApps
      */
      function doUpload(fileName, e) {
        return new Promise((resolve, reject) => {
          showProgressMessage("Carregando: <span style='color: #00F498 ;'>"   "0%</span>");
          var chunks = e.chunks;
          var location = e.location;
          var cnt = 0;
          var end = chunks.length;
          var temp = function callback(cnt) {
            var e = chunks[cnt];
            var xhr = new XMLHttpRequest();
            xhr.open("PUT", location, true);
            console.log("content range: ", e.range);
            xhr.setRequestHeader('Content-Range', e.range);
            xhr.send(e.data);
            xhr.onloadend = function() {
                var status = xhr.status;
                cnt  = 1;
                console.log("Uploading: "   status   " ("   cnt   " / "   end   ")");
                showProgressMessage("Carregando: <span style='color: #00F498 ;'>" 
                         Math.floor(100 * cnt / end)   "%</span>" );
                if (status == 308) {
                    callback(cnt);
                } else if (status == 200) {
                    $("#progress").text("Done.");
                    resolve();
                } else {
                    $("#progress").text("Error: "   xhr.response);
                    reject();
                }
            };
          }(cnt);
        });
      }

      /**
      * Helper functions modified from:
      * https://github.com/tanaikech/Resumable_Upload_For_WebApps
      */
      function getChunkpot(chunkSize, fileSize) {
        var chunkPot = {};
        chunkPot.total = fileSize;
        chunkPot.chunks = [];
        if (fileSize > chunkSize) {
            var numE = chunkSize;
            var endS = function(f, n) {
                var c = f % n;
                if (c == 0) {
                    return 0;
                } else {
                    return c;
                }
            }(fileSize, numE);
            var repeat = Math.floor(fileSize / numE);
            for (var i = 0; i <= repeat; i  ) {
                var startAddress = i * numE;
                var c = {};
                c.startByte = startAddress;
                if (i < repeat) {
                    c.endByte = startAddress   numE - 1;
                    c.numByte = numE;
                    chunkPot.chunks.push(c);
                } else if (i == repeat && endS > 0) {
                    c.endByte = startAddress   endS - 1;
                    c.numByte = endS;
                    chunkPot.chunks.push(c);
                }
            }
        } else {
            var chunk = {
                startByte: 0,
                endByte: fileSize - 1,
                numByte: fileSize,
            };
            chunkPot.chunks.push(chunk);
        }
        return chunkPot;
      }

    </script>

  </body>

</html>

As you can see, currently the upload folder is hardcoded in the script and is defined in the variable var upload_folder = "01-01".

Taking a look at my form, notice that I have a field called Model, this field is important to define the model of the client's request.

enter image description here

The issue is that each folder should only contain a single file, so I created several upload folders following this pattern:

01-01, 01-02, 01-03, 02-01, 02-02, 02-03...

The first number refers to the model selected on the form, the second number refers to the slot used.

enter image description here

What I need is that, according to the model selected in the form, the script checks which folder referring to that model does not yet have a file and uploads the video file to that folder.

Exemplifying

Model 01 was selected in the form

The script checks if folder 01-01 has a file, if it does, it uploads the file to folder 01-02, if that is also busy, it uploads the file to folder 01-03.

Model 02 was selected in the form

The script checks if folder 02-01 has a file, if it does, it uploads the file to folder 02-02, if that is also busy, it uploads the file to folder 02-03.

I'm a beginner in JavaScript, so I have some difficulty with this type of operation involving Client and Server... besides, I couldn't think of any way to get to this result without filling the code with IF/Else.

I would be very grateful with any help.

EDIT

After Tanaike's suggestion, the script is close to working as expected, the issue is that for some reason the first folder is generated with an extra -01 looking like this:

enter image description here

Another point is that when I change the option in the form, for example 02, the folder taken into account continues to be 01, that is, even if 02 is selected in the form, the script uploads the file to the next empty folder 01.

enter image description here

I tried to modify the variable var upload_folder = "01-01"; to var upload_folder = $('#Model').val(); but this way the folder created is Choose...-01, I believe this happens because it would be necessary to return the information from $('#Model').val() to the .gs file somehow.

I would also like that when the third folder of the selected model is exceeded, the script displays an error popup instead of continuing to create more folders, that is, if the option 01 is selected in the form's model field and folders 01-01, 01-02, 01-03 are full, it is not to create a 01-04 but to display a message like Wait for model 01 release.

I've made the upload folder access free so you can see exactly what's going on:

Stack Thread - Demonstration Folder

CodePudding user response:

From your following expected result,

  • each folder should only contain a single file
  • The script checks if folder 01-01 has a file, if it does, it uploads the file to folder 01-02, if that is also busy, it uploads the file to folder 01-03.
  • The script checks if folder 02-01 has a file, if it does, it uploads the file to folder 02-02, if that is also busy, it uploads the file to folder 02-03.

How about modifying your script as follows?

Modified script:

In this modification, your function createOrGetFolder is modified.

function createOrGetFolder(folderName, parentFolderId) {
  try {
    var parentFolder = DriveApp.getFolderById(parentFolderId), folder;
    if (parentFolder) {
      var foldersIter = parentFolder.getFoldersByName("Video");
      if (foldersIter.hasNext()) {
        var videoFolder = foldersIter.next();
        var nextFolderName = folderName   "-01";
        while (!folder) {
          video_folder = videoFolder.getFoldersByName(nextFolderName);
          if (video_folder.hasNext()) {
            folder = video_folder.next();
            var files = folder.getFiles();
            if (files.hasNext()) {
              var [a, b] = nextFolderName.split("-");
              nextFolderName = `${a}-${String(Number(b)   1).padStart(2, "0")}`;
              folder = null;
            }
          } else {
            folder = videoFolder.createFolder(nextFolderName);
          }
        }
      } else {
        folder = parentFolder.createFolder("Video");
        folder = folder.createFolder(folderName);
      }
    } else {
      throw new Error("Parent Folder with id: "   parentFolderId   " not found");
    }
    return folder.getId();
  } catch (error) {
    return error;
  }
}
  • When I saw your HTML, for example, when 02 is selected from "Model", the value of it is 02. So, in this modification, in order to scan the folders and files in each folder, I used 02-01 as the 1st search folder.
  • By this modification, for example, when folderName is 02, the folder names of 02-01, 02-02,,, are searched. When the files are not found in the folder, the folder ID is returned.

Note:

Added:

About 01-01-01 folder, in this case, it is considered that in your javascript, you are retrieving the value from the dropdown list with var model = $('#Model').val(); (in this case, the values are 01, 02, 03,,,), but, as the default folder name, you are using var upload_folder = "01-01". I think that this is the reason for your new 1st issue. So, please modify var upload_folder = "01-01" to var upload_folder = "01".

And, in your javascript, by var newFolderName = upload_folder, the same folder name of upload_folder is always used. I think that this is the reason for your new 2nd issue. So, please modify them as follows.

From:

var upload_folder = "01-01"

To:

var upload_folder = "01";

And,

From:

var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
var color = $('#Color').val();

To:

var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
  • Related