├── .gitignore ├── Ac.Net.Authentication ├── .gitignore ├── APSClientHelper.cs ├── Ac.Net.Authentication.csproj ├── EncryptionHelper.cs ├── FodyWeavers.xml ├── Html.cs ├── Models │ ├── AuthParameters.cs │ ├── IAuthParamProvider.cs │ ├── ITokenManager.cs │ └── TokenData.cs └── ThreeLeggedTokenManager.cs ├── ApsSettings.Data ├── ApsSettings.Data.csproj ├── DataContext.cs ├── DataUtils │ └── SettingsUtiltities.cs ├── Migrations │ ├── 20230824162133_InitialMigration.Designer.cs │ ├── 20230824162133_InitialMigration.cs │ ├── 20230831165452_AddBulkDownloadTables.Designer.cs │ ├── 20230831165452_AddBulkDownloadTables.cs │ ├── 20230831172112_EditBulkDownloadTables.Designer.cs │ ├── 20230831172112_EditBulkDownloadTables.cs │ ├── 20230831192257_MoreChangesToDownloadTables.Designer.cs │ ├── 20230831192257_MoreChangesToDownloadTables.cs │ ├── 20230831192723_RemoveFieldFromDownloadJobTable.Designer.cs │ ├── 20230831192723_RemoveFieldFromDownloadJobTable.cs │ ├── 20230831205634_ChangeProp.Designer.cs │ ├── 20230831205634_ChangeProp.cs │ ├── 20230901155709_AddObjectIdColToDownloadFilesTable.Designer.cs │ ├── 20230901155709_AddObjectIdColToDownloadFilesTable.cs │ ├── 20231113165259_DownloadVirtualStatus.Designer.cs │ ├── 20231113165259_DownloadVirtualStatus.cs │ └── DataContextModelSnapshot.cs └── Models │ ├── BatchJob.cs │ ├── BulkDownload.cs │ ├── BulkDownloadFile.cs │ ├── BulkOpsConfig.cs │ ├── BulkUpload.cs │ ├── BulkUploadAutodeskMirror.cs │ ├── BulkUploadAutodeskMirrorFile.cs │ ├── BulkUploadFile.cs │ ├── BulkUploadPreset.cs │ └── Settings.cs ├── Bulk-Uploader-FrontEnd ├── AppSettings.cs ├── Bulk-File-Manager.csproj ├── Bulk-Uploader-FrontEnd.sln ├── ClientApp │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── autodesk-logo-blk.svg │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── Components │ │ │ ├── AuthenticationButton.tsx │ │ │ ├── BatchStatus.tsx │ │ │ ├── BatchUiEntry.tsx │ │ │ ├── BrowseCloudLocation.tsx │ │ │ ├── ColoredStatusButton.tsx │ │ │ ├── FolderSelector.tsx │ │ │ ├── Layout.tsx │ │ │ ├── Loading.tsx │ │ │ ├── ResponsiveAppBar.tsx │ │ │ ├── SimpleDate.tsx │ │ │ └── projectTree.tsx │ │ ├── Images │ │ │ └── AutodeskIcon.tsx │ │ ├── Models │ │ │ ├── BatchEntry.ts │ │ │ ├── BatchJob.ts │ │ │ ├── BatchProcessPayload.ts │ │ │ └── ProjectDownload.ts │ │ ├── Pages │ │ │ ├── Batches │ │ │ │ └── BatchLists.tsx │ │ │ ├── BulkDownloads │ │ │ │ ├── BulkDownloadSingleHeader.tsx │ │ │ │ ├── DownloadJobFileList.tsx │ │ │ │ ├── DownloadSingle.tsx │ │ │ │ ├── DownloadSingleStatus.tsx │ │ │ │ ├── DownloadsCreate.tsx │ │ │ │ └── DownloadsList.tsx │ │ │ ├── BulkUploads │ │ │ │ ├── BulkUploadCreate.tsx │ │ │ │ ├── BulkUploadList.tsx │ │ │ │ ├── BulkUploadSingle.tsx │ │ │ │ ├── BulkUploadSingleHeader.tsx │ │ │ │ └── BulkUploadSingleStatus.tsx │ │ │ ├── InitialSetupPage.tsx │ │ │ ├── LoadingPage.tsx │ │ │ ├── SelectionsPage.tsx │ │ │ ├── SettingsPage.tsx │ │ │ └── UtilitiesPage.tsx │ │ ├── Utilities │ │ │ ├── Hooks │ │ │ │ ├── useBatches.ts │ │ │ │ ├── useBulkDownload.ts │ │ │ │ ├── useBulkDownloadStatus.ts │ │ │ │ ├── useBulkDownloads.ts │ │ │ │ ├── useBulkUpload.ts │ │ │ │ ├── useBulkUploadPresets.ts │ │ │ │ ├── useBulkUploadStatus.ts │ │ │ │ ├── useBulkUploads.ts │ │ │ │ ├── useDataManagement.ts │ │ │ │ ├── useMessageListener.tsx │ │ │ │ ├── useScripting.ts │ │ │ │ ├── useSettings.ts │ │ │ │ └── useThreeLegged.ts │ │ │ ├── RenderTree.ts │ │ │ └── SimpleREST.ts │ │ ├── assets │ │ │ └── react.svg │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── Controllers │ ├── BatchProcessingController.cs │ ├── BulkDownloadController.cs │ ├── BulkUploadController.cs │ ├── BulkUploadPresetController.cs │ ├── DataManagementController.cs │ ├── ScriptingController.cs │ ├── SettingsController.cs │ └── UtilityControllers.cs ├── Excel │ └── ExcelUtils.cs ├── Flows │ ├── BatchFlows.cs │ └── UtilityFlows.cs ├── HangfireJobs.cs ├── Helpers │ ├── APSClientHelper.cs │ ├── APSHelpers.cs │ ├── CreateSelfSignedCertificate.cs │ ├── FileMetricsHelper.cs │ ├── FolderHelpers.cs │ ├── JointTokenManager.cs │ └── TwoLeggedTokenManager.cs ├── Jobs │ └── DownloaderHangfireJobs.cs ├── Models │ ├── Account.cs │ ├── BusinessUnit.cs │ ├── ErrorMessage.cs │ ├── Project.cs │ ├── SimpleFile.cs │ └── SimpleFolder.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Resources │ └── autodesk_icon.ico ├── Startup.cs ├── Utils │ ├── BatchJobController.cs │ ├── BatchManager.cs │ ├── BatchRegistry.cs │ ├── FlurlSettings.cs │ ├── MappingProfiles.cs │ ├── PluginManager.cs │ ├── PollyService.cs │ └── SettingsProvider.cs ├── Views │ ├── MainForm.Designer.cs │ ├── MainForm.cs │ └── MainForm.resx ├── appsettings.Development.json ├── appsettings.json ├── electron.manifest.json └── wwwroot │ ├── css │ └── site.css │ ├── favicon.ico │ ├── js │ └── site.js │ └── lib │ ├── bootstrap │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-grid.rtl.css │ │ ├── bootstrap-grid.rtl.css.map │ │ ├── bootstrap-grid.rtl.min.css │ │ ├── bootstrap-grid.rtl.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap-reboot.rtl.css │ │ ├── bootstrap-reboot.rtl.css.map │ │ ├── bootstrap-reboot.rtl.min.css │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ ├── bootstrap-utilities.css │ │ ├── bootstrap-utilities.css.map │ │ ├── bootstrap-utilities.min.css │ │ ├── bootstrap-utilities.min.css.map │ │ ├── bootstrap-utilities.rtl.css │ │ ├── bootstrap-utilities.rtl.css.map │ │ ├── bootstrap-utilities.rtl.min.css │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ ├── bootstrap.rtl.css │ │ ├── bootstrap.rtl.css.map │ │ ├── bootstrap.rtl.min.css │ │ └── bootstrap.rtl.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.esm.js │ │ ├── bootstrap.esm.js.map │ │ ├── bootstrap.esm.min.js │ │ ├── bootstrap.esm.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── jquery-validation-unobtrusive │ ├── LICENSE.txt │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── BulkUploaderUtils ├── AppSettings.cs ├── BulkUploaderUtils.csproj ├── Excel │ └── ExcelUtils.cs ├── Flows │ ├── DownloadFlows.cs │ └── UtilityFlows.cs ├── Helpers │ ├── CreateSelfSignedCertificate.cs │ ├── FileMetricsHelper.cs │ ├── FolderHelpers.cs │ ├── ForgeHelpers.cs │ ├── HubHelper.cs │ └── TokenManager.cs ├── Models │ ├── Account.cs │ ├── BusinessUnit.cs │ ├── ErrorMessage.cs │ ├── FolderPermission.cs │ ├── Forge │ │ ├── ForgeAttributesBatchGet.cs │ │ ├── ForgeBim360Project.cs │ │ ├── ForgeBusinessUnits.cs │ │ ├── ForgeCheckPermissionResponse.cs │ │ ├── ForgeCreateFolder.cs │ │ ├── ForgeFirstVersionResponse.cs │ │ ├── ForgeFolderContents.cs │ │ ├── ForgeGetDataHooksResponse.cs │ │ ├── ForgeHub.cs │ │ ├── ForgeItemTipResponse.cs │ │ ├── ForgeMetadata.cs │ │ ├── ForgeMetadataProperties.cs │ │ ├── ForgeProjectUsers.cs │ │ ├── ForgeProjects.cs │ │ ├── ForgeS3UploadCompletion.cs │ │ ├── ForgeSignedS3Upload.cs │ │ ├── ForgeSignedS3Url.cs │ │ ├── ForgeStorageCreation.cs │ │ ├── ForgeSupportedFormats.cs │ │ ├── ForgeTopFolders.cs │ │ ├── ForgeUserInformation.cs │ │ ├── ForgeVersions.cs │ │ ├── ForgeWebhookBody.cs │ │ └── ForgeWebhookResponse.cs │ ├── ForgeBatchS3Download.cs │ ├── Project.cs │ ├── SimpleFile.cs │ ├── SimpleFolder.cs │ ├── SimpleHook.cs │ └── ThreeLeggedToken.cs └── Utils │ ├── FlurlSettings.cs │ ├── MappingProfiles.cs │ └── PollyService.cs ├── Documentation ├── 20230817-Upload-Download-Coordination.drawio ├── BalsamiqWireframes.bmpr ├── Readme_img.png ├── Thumnail1.gif ├── Thumnail_bulk.gif ├── User_guide_0.png ├── User_guide_1.png ├── User_guide_2.png ├── User_guide_3.png ├── User_guide_4.png ├── User_guide_5.png ├── User_guide_6.png ├── Wireframes.pdf ├── developer-guide.md └── user-guide.md ├── LICENSE ├── License.md ├── MigrationPlugin ├── Batches │ ├── ApsDownloadBatch.cs │ ├── ApsMigrateBatch.cs │ └── ApsUploadBatch.cs ├── Controllers │ ├── BatchBasicMigrationController.cs │ ├── BatchDownloadController.cs │ ├── BatchUploadController.cs │ └── MigrationPluginControllers.cs ├── Excel │ └── ExcelUtils.cs ├── MigrationPlugin.csproj ├── Models │ ├── BatchJob.cs │ └── IBatchRegistry.cs └── Plugin │ └── MigrationPluginObj.cs ├── PackageApplication.ps1 ├── PluginBase ├── ISettingsProvider.cs ├── Models │ ├── BatchDetails.cs │ ├── BatchJob.cs │ ├── BatchProcessPayload.cs │ ├── BatchProcessResults.cs │ ├── IBatchRegistry.cs │ ├── IJobExecutionPlugin.cs │ ├── JobTaskAggregator.cs │ ├── StepHistory.cs │ └── TaskStep.cs └── PluginBase.csproj ├── README.md ├── TestPlugin ├── Batches │ └── TestDownloadBatch.cs ├── Controllers │ └── TestBatchController.cs ├── ExelUtils │ └── ExcelUtils.cs ├── Models │ └── TestDownload.cs ├── Plugin │ └── TestPluginObj.cs └── TestPlugin.csproj └── bulk-file-manager.sln /Ac.Net.Authentication/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj -------------------------------------------------------------------------------- /Ac.Net.Authentication/APSClientHelper.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Authentication; 2 | using Autodesk.SDKManager; 3 | 4 | namespace Ac.Net.Authentication 5 | { 6 | internal class APSClientHelper 7 | { 8 | internal static SDKManager SdkManager => SdkManagerBuilder.Create().Build(); 9 | internal static AuthenticationClient AuthClient => new AuthenticationClient(SdkManager); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ac.Net.Authentication/Ac.Net.Authentication.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | true 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Ac.Net.Authentication/EncryptionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | 7 | namespace Ac.Net.Authentication 8 | { 9 | public static class EncryptionHelper 10 | { 11 | private static readonly string key = "sshsroshelpdotnetmania2422key1d3"; 12 | 13 | public static string Encrypt(string text) 14 | { 15 | byte[] iv = new byte[16]; 16 | byte[] array; 17 | using (Aes aes = Aes.Create()) 18 | { 19 | aes.Key = Encoding.UTF8.GetBytes(key); 20 | aes.IV = iv; 21 | ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); 22 | using (MemoryStream ms = new MemoryStream()) 23 | { 24 | using (CryptoStream cryptoStream = new CryptoStream((Stream)ms, encryptor, CryptoStreamMode.Write)) 25 | { 26 | using (StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream)) 27 | { 28 | streamWriter.Write(text); 29 | } 30 | 31 | array = ms.ToArray(); 32 | } 33 | } 34 | } 35 | return Convert.ToBase64String(array); 36 | } 37 | 38 | public static string Decrypt(string text) 39 | { 40 | byte[] iv = new byte[16]; 41 | byte[] buffer = Convert.FromBase64String(text); 42 | using (Aes aes = Aes.Create()) 43 | { 44 | aes.Key = Encoding.UTF8.GetBytes(key); 45 | aes.IV = iv; 46 | ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); 47 | using (MemoryStream ms = new MemoryStream(buffer)) 48 | { 49 | using (CryptoStream cryptoStream = new CryptoStream((Stream)ms, decryptor, CryptoStreamMode.Read)) 50 | { 51 | using (StreamReader streamReader = new StreamReader((Stream)cryptoStream)) 52 | { 53 | return streamReader.ReadToEnd(); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Ac.Net.Authentication/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Ac.Net.Authentication/Html.cs: -------------------------------------------------------------------------------- 1 | namespace Ac.Net.Authentication 2 | { 3 | internal class Html 4 | { 5 | internal static string successHtml = @" 6 | 7 | Online HTML Editor 8 | 29 | 30 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bulk-file-manager", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "start": "vite", 8 | "dev": "vite", 9 | "build": "tsc && vite build", 10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "@emotion/react": "^11.11.1", 15 | "@emotion/styled": "^11.11.0", 16 | "@mui/icons-material": "^5.14.3", 17 | "@mui/lab": "^5.0.0-alpha.139", 18 | "@mui/material": "^5.14.3", 19 | "@mui/x-data-grid": "^6.11.0", 20 | "@tanstack/react-query": "^4.32.1", 21 | "@tanstack/react-query-devtools": "^4.32.6", 22 | "@types/react-router": "^5.1.20", 23 | "@types/react-router-dom": "^5.3.3", 24 | "@uidotdev/usehooks": "^2.4.1", 25 | "axios": "^1.4.0", 26 | "mobx-react": "^9.0.0", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-router": "^6.14.2", 30 | "react-router-dom": "^6.14.2", 31 | "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.2.15", 35 | "@types/react-dom": "^18.2.7", 36 | "@typescript-eslint/eslint-plugin": "^6.0.0", 37 | "@typescript-eslint/parser": "^6.0.0", 38 | "@vitejs/plugin-react": "^4.0.3", 39 | "eslint": "^8.45.0", 40 | "eslint-plugin-react-hooks": "^4.6.0", 41 | "eslint-plugin-react-refresh": "^0.4.3", 42 | "typescript": "^5.0.2", 43 | "vite": "^4.4.5", 44 | "vite-plugin-mkcert": "^1.15.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/public/autodesk-logo-blk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | width: 100vw; 3 | height: 100vh; 4 | } 5 | 6 | main { 7 | padding: 1em; 8 | } 9 | 10 | code{ 11 | color: #e22bca; 12 | white-space: pre-wrap; 13 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | import {useSettings} from "./Utilities/Hooks/useSettings.ts"; 3 | import {Navigate, Route, Routes, useLocation} from "react-router"; 4 | import {BulkUploadCreate} from "./Pages/BulkUploads/BulkUploadCreate.tsx"; 5 | import { SettingsPage } from "./Pages/SettingsPage.tsx"; 6 | import { BulkUploadList } from "./Pages/BulkUploads/BulkUploadList.tsx"; 7 | import {LoadingPage} from "./Pages/LoadingPage.tsx"; 8 | import {Layout} from "./Components/Layout.tsx"; 9 | import {BulkUploadSingle} from "./Pages/BulkUploads/BulkUploadSingle.tsx"; 10 | import {DownloadSingle} from "./Pages/BulkDownloads/DownloadSingle"; 11 | import {BulkUploadSingleStatus} from "./Pages/BulkUploads/BulkUploadSingleStatus.tsx"; 12 | import { UtilitiesPage } from './Pages/UtilitiesPage.tsx'; 13 | import { SelectionsPage } from './Pages/SelectionsPage.tsx'; 14 | import {InitialSetupPage} from "./Pages/InitialSetupPage"; 15 | import { BatchList } from './Pages/Batches/BatchLists.tsx'; 16 | import {DownloadsCreate} from "./Pages/BulkDownloads/DownloadsCreate"; 17 | import {DownloadsList} from "./Pages/BulkDownloads/DownloadsList"; 18 | import {DownloadSingleStatus} from "./Pages/BulkDownloads/DownloadSingleStatus"; 19 | import {useIsAuthenticated, useThreeLegged} from "./Utilities/Hooks/useThreeLegged.ts"; 20 | 21 | export default function App() { 22 | const {dataHash: settingsHash, isLoading} = useSettings(); 23 | const {isLoading: threeLeggedLoading} = useThreeLegged(); 24 | const {isAuthenticated, isLoading: isAuthenticatedLoading} = useIsAuthenticated(); 25 | 26 | if (isLoading || threeLeggedLoading) return 27 | if (!settingsHash['ClientId']?.value || !settingsHash['ClientSecret']?.value) return 28 | 29 | return ( 30 | 31 | }> 32 | }/> 33 | }/> 34 | }/> 35 | }/> 36 | }/> 37 | }/> 38 | }/> 39 | }/> 40 | }/> 41 | }/> 42 | }/> 43 | }/> 44 | 45 | }/> 46 | 47 | ) 48 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Components/AuthenticationButton.tsx: -------------------------------------------------------------------------------- 1 | import {Button, ButtonGroup} from "@mui/material"; 2 | import {useIsAuthenticated, useThreeLegged} from "../Utilities/Hooks/useThreeLegged.ts"; 3 | import IconButton from "@mui/material/IconButton"; 4 | import {Sync} from "@mui/icons-material"; 5 | import {useNavigate} from "react-router"; 6 | import React from "react"; 7 | import Box from "@mui/material/Box"; 8 | 9 | 10 | 11 | export function AuthenticationButton() { 12 | const {isAuthenticated} = useIsAuthenticated(); 13 | const {authUrl, logout} = useThreeLegged(); 14 | const navigate = useNavigate(); 15 | 16 | function login(e: React.SyntheticEvent) { 17 | e.preventDefault(); 18 | 19 | // http://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen 20 | // Fixes dual-screen position Most browsers Firefox 21 | const w = 600; 22 | const h = 800; 23 | 24 | const dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : 0; 25 | const dualScreenTop = window.screenTop != undefined ? window.screenTop : 0; 26 | 27 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; 28 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; 29 | 30 | const left = ((width / 2) - (w / 2)) + dualScreenLeft; 31 | const top = ((height / 2) - (h / 2)) + dualScreenTop; 32 | const newWindow = window.open(authUrl, '_blank', 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left); 33 | 34 | newWindow?.focus(); 35 | } 36 | 37 | return ( 38 | 39 | 44 | 45 | 50 | 51 | 52 | window.location.reload()}/> 53 | 54 | ) 55 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Components/BatchStatus.tsx: -------------------------------------------------------------------------------- 1 | import Button from "@mui/material/Button"; 2 | import { BatchJob } from "../Models/BatchJob"; 3 | import { BatchProcessPayload } from "../Models/BatchProcessPayload"; 4 | import axios, { Axios } from "axios"; 5 | 6 | 7 | export function BatchStatus({ batchJob }: { 8 | batchJob: BatchJob; 9 | 10 | }) { 11 | const payload = new BatchProcessPayload(batchJob.id, batchJob.type, "queued", "Queue Jobs"); 12 | let btncolor: "primary" | "info" | "secondary" = "primary"; 13 | let complete = false; 14 | if (batchJob.queued == null){ 15 | // 16 | } else if (batchJob.started === null) { 17 | payload.buttonLabel= "Start"; 18 | payload.action = "start"; 19 | btncolor = "secondary"; 20 | } else if (batchJob.completed !== null) { 21 | payload.buttonLabel = ""; 22 | payload.action = ""; 23 | btncolor = "info"; 24 | } 25 | return ( 26 |
27 | {(!complete) ? 28 | : null} 37 | 56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Components/BrowseCloudLocation.tsx: -------------------------------------------------------------------------------- 1 | import Button from "@mui/material/Button"; 2 | import React, {useState} from "react"; 3 | import {Dialog} from "@mui/material"; 4 | import {ICloudFolderProps, ProjectTree} from "./projectTree.tsx"; 5 | 6 | export function BrowseCloudLocation({onFolderChange, type}: { 7 | type: 'upload' | 'download'; 8 | onFolderChange: ({hubId, projectId, folderId, folderPath}: ICloudFolderProps) => void}) { 9 | const [openDiag, setOpenDiag] = useState(false); 10 | 11 | return ( 12 | <> 13 | 18 | 19 | setOpenDiag(false)} 23 | > 24 | setOpenDiag(false)} 28 | /> 29 | 30 | 31 | ) 32 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Components/ColoredStatusButton.tsx: -------------------------------------------------------------------------------- 1 | import Button from "@mui/material/Button"; 2 | import { NavLink } from "react-router-dom"; 3 | 4 | 5 | export function ColoredStatusButton({ color, count, path, title }: { 6 | color: "inherit" | "primary" | "secondary" | "success" | "error" | "info" | "warning", 7 | count?: number, 8 | path: string, 9 | title?: string 10 | }) { 11 | return ( 12 | 22 | ) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import {Outlet} from "react-router"; 2 | import ResponsiveAppBar from "./ResponsiveAppBar.tsx"; 3 | 4 | export const Layout = ()=>{ 5 | return <> 6 | 7 | 8 |
9 | 10 |
11 | 12 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CircularProgress from "@mui/material/CircularProgress"; 3 | import "../App.css"; 4 | import { observer } from "mobx-react"; 5 | 6 | export interface LoadingProps { 7 | size?: number; 8 | } 9 | const Loading = observer((props: LoadingProps) => { 10 | return ( 11 | <> 12 | 13 | 14 | ); 15 | }); 16 | 17 | export default Loading; 18 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Components/SimpleDate.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const SimpleDate = ({date}: {date: string})=>{ 4 | if(!date) return <>--- 5 | const dateString = new Date(date)?.toLocaleString(); 6 | return (
7 | {dateString} 8 |
) 9 | } 10 | 11 | export const SimpleDate2 = ({date}: {date: string})=>{ 12 | if(!date) return <>--- 13 | const d = new Date(date); 14 | const dstr = [ 15 | 16 | ('0' + (d.getMonth() + 1)).slice(-2), 17 | ('0' + d.getDate()).slice(-2), 18 | d.getFullYear().toString().slice(-2) 19 | ].join('.') + " " + [ 20 | d.getHours().toString().slice(-2), 21 | ('0' + d.getMinutes()).slice(-2) 22 | ].join(':') 23 | 24 | 25 | return (
26 | {dstr} 27 |
) 28 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Images/AutodeskIcon.tsx: -------------------------------------------------------------------------------- 1 | import {SvgIcon, SvgIconProps} from "@mui/material"; 2 | 3 | export const AutodeskIcon = (params: SvgIconProps) => { 4 | 5 | return ( 6 | 7 | 15 | 22 | 23 | 24 | ) 25 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Models/BatchEntry.ts: -------------------------------------------------------------------------------- 1 | export class BatchEntry 2 | { 3 | title: string = ""; 4 | key: string = ""; 5 | description: string = ""; 6 | batchLoadUrl: string = ""; 7 | batchTemplateUrl: string = ""; 8 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Models/BatchJob.ts: -------------------------------------------------------------------------------- 1 | export interface BatchJob { 2 | id: number; 3 | name: string; 4 | type: string; 5 | errors: string | null; 6 | jobCount: number; 7 | data: string | null; 8 | created: string | null; 9 | queued: string | null; 10 | started: string | null; 11 | completed: string | null; 12 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Models/BatchProcessPayload.ts: -------------------------------------------------------------------------------- 1 | export class BatchProcessPayload { 2 | 3 | 4 | constructor (dbId: number = 0, batchType: string= "", action: string = "", buttonLabel: string = "") 5 | { 6 | this.dbId = dbId; 7 | this.batchType = batchType; 8 | this.buttonLabel = buttonLabel; 9 | this.action = action; 10 | } 11 | 12 | dbId: number; 13 | batchType: string ; 14 | action: string; 15 | buttonLabel: string ; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Models/ProjectDownload.ts: -------------------------------------------------------------------------------- 1 | export class ProjectDownload { 2 | constructor (sourceHub: string, sourceProject: string, downloadFolder: string, includeProject: boolean, ignoreExtensions: string, ignoreFolders: string) 3 | { 4 | this.sourceHub = sourceHub; 5 | this.sourceProject = sourceProject; 6 | this.downloadFolder = downloadFolder; 7 | this.includeProject = includeProject; 8 | this.ignoreExtensions = ignoreExtensions; 9 | this.ignoreFolders = ignoreFolders; 10 | } 11 | 12 | sourceHub: string; 13 | sourceProject: string; 14 | includeProject: boolean; 15 | downloadFolder: string; 16 | ignoreExtensions: string | null; 17 | ignoreFolders: string | null; 18 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Pages/Batches/BatchLists.tsx: -------------------------------------------------------------------------------- 1 |  2 | import {DataGrid, GridColDef} from "@mui/x-data-grid"; 3 | import {useNavigate} from "react-router"; 4 | import IconButton from "@mui/material/IconButton"; 5 | import SyncIcon from "@mui/icons-material/Sync"; 6 | import {SimpleDate2} from "../../Components/SimpleDate.tsx"; 7 | import {useState} from "react"; 8 | import {Button, Container} from "@mui/material"; 9 | import { useBatches } from "../../Utilities/Hooks/useBatches.ts"; 10 | import { BatchJob } from "../../Models/BatchJob.ts"; 11 | import { BatchStatus } from "../../Components/BatchStatus.tsx"; 12 | 13 | export const BatchList = () => { 14 | const navigate = useNavigate() 15 | const {data: batches, isLoading, refresh} = useBatches(); 16 | const [selected, setSelected] = useState([]) 17 | 18 | 19 | 20 | const columns: GridColDef[] = [ 21 | {field: 'name', headerName: 'Name', flex: 2}, 22 | {field: 'type', headerName: 'Type', flex: 1}, 23 | {field: 'jobCount ', headerName: 'Task Count', flex: 1}, 24 | {field: 'created', headerName: 'Created On', flex: 1, renderCell: ({value})=>()}, 25 | {field: 'queued', headerName: 'Queued On', flex: 1, renderCell: ({value})=>()}, 26 | {field: 'started', headerName: 'Started On', flex: 1, renderCell: ({value})=>()}, 27 | {field: 'completed', headerName: 'Completed On', flex: 1, renderCell: ({value})=>()}, 28 | 29 | { 30 | field: 'id', headerName: 'Action', width: 200, align: "right", 31 | renderCell: ({row}) => ( 32 | 33 | ) 34 | }, 35 | 36 | ] 37 | 38 | return <> 39 |
40 |

Active Batches

41 |
42 | {!!selected.length && 43 | } 44 | 45 | 46 | 47 |
48 |
49 | 50 | setSelected(rowSelectionModel as number[])} 59 | /> 60 | 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Pages/BulkDownloads/BulkDownloadSingleHeader.tsx: -------------------------------------------------------------------------------- 1 | import {NavLink} from "react-router-dom"; 2 | import {ColoredStatusButton} from "../../Components/ColoredStatusButton.tsx"; 3 | import IconButton from "@mui/material/IconButton"; 4 | import SyncIcon from "@mui/icons-material/Sync"; 5 | import {LinearProgress} from "@mui/material"; 6 | import {IBulkDownload} from "../../Utilities/Hooks/useBulkDownloads.ts"; 7 | 8 | export const BulkDownloadSingleHeader = ({bulkDownload, refresh}: { bulkDownload?: IBulkDownload, refresh?: () => void }) => { 9 | 10 | console.log(bulkDownload); 11 | 12 | return ( 13 | <> 14 | {bulkDownload ?
15 |

16 | Bulk Downloads 17 | : 18 | {bulkDownload?.name} 19 |

20 |

{bulkDownload?.status}

21 | 22 |
23 |
24 | 30 | 35 | 41 | 47 | {refresh && 48 | 49 | } 50 |
51 |
: } 52 | 53 | ) 54 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Pages/BulkDownloads/DownloadJobFileList.tsx: -------------------------------------------------------------------------------- 1 | import {DataGrid, GridColDef} from "@mui/x-data-grid"; 2 | import {IBulkDownload, IBulkDownloadFile} from "../../Utilities/Hooks/useBulkDownloads"; 3 | import {bytesToSize} from "./DownloadSingleStatus"; 4 | 5 | const columns: GridColDef[] = [ 6 | {field: 'fileName', headerName: 'File Name', flex:1}, 7 | {field: 'sourceFilePath', headerName: 'Source File Path', flex:1}, 8 | {field: 'destinationFilePath', headerName: 'Download Path', flex:1}, 9 | {field: 'fileSize', headerName: 'File Size', sortable: false, width:100, type: 'number', 10 | renderCell: ({row})=>bytesToSize(row.fileSize)}, 11 | {field: 'status', headerName: 'Status', width:80}, 12 | ] 13 | 14 | export const DownloadJobFileList = ({download}: {download: IBulkDownload}) => { 15 | return ( 16 | 17 | ) 18 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Pages/BulkUploads/BulkUploadSingleHeader.tsx: -------------------------------------------------------------------------------- 1 | import {IBulkUpload} from "../../Utilities/Hooks/useBulkUploads.ts"; 2 | import {NavLink} from "react-router-dom"; 3 | import {ColoredStatusButton} from "../../Components/ColoredStatusButton.tsx"; 4 | import IconButton from "@mui/material/IconButton"; 5 | import SyncIcon from "@mui/icons-material/Sync"; 6 | import {LinearProgress} from "@mui/material"; 7 | 8 | export const BulkUploadSingleHeader = ({bulkUpload, refresh}: { bulkUpload?: IBulkUpload, refresh?: () => void }) => { 9 | 10 | return ( 11 | <> 12 | {bulkUpload ?
13 |

14 | Bulk Uploads 15 | : 16 | {bulkUpload?.name} 18 |

19 |

{bulkUpload?.status}

20 | 21 |
22 |
23 | 29 | 35 | 41 | 47 | 53 | {refresh && 54 | 55 | } 56 |
57 |
: } 58 | 59 | ) 60 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Pages/LoadingPage.tsx: -------------------------------------------------------------------------------- 1 | import {CircularProgress} from "@mui/material"; 2 | 3 | export const LoadingPage = ()=>{ 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Pages/SelectionsPage.tsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { BatchUiEntry } from "../Components/BatchUiEntry"; 3 | import { BatchEntry } from "../Models/BatchEntry"; 4 | import { SimpleGet } from "../Utilities/SimpleREST"; 5 | 6 | 7 | 8 | 9 | export const SelectionsPage = () => { 10 | 11 | 12 | const { data, isLoading, isFetching } = useQuery({ 13 | queryKey: ['batch'], 14 | queryFn: () => SimpleGet(`/api/batch`) 15 | }) 16 | 17 | if (isLoading) { 18 | return
Loading...
19 | } 20 | 21 | if (isFetching) { 22 | return
Loading...
23 | } 24 | 25 | // const items: ReadonlyArray [...data] 26 | return ( 27 | ( 28 |
29 |

Operation Selections

30 | 31 |
39 | 40 | {data?.map(p => ())} 41 |
42 |
43 | 44 | ) 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useBatches.ts: -------------------------------------------------------------------------------- 1 | import { useQuery, useQueryClient } from "@tanstack/react-query"; 2 | import { SimpleGet } from "../SimpleREST.ts"; 3 | 4 | import { BatchJob } from "../../Models/BatchJob.ts"; 5 | 6 | 7 | export function useBatches() { 8 | const queryClient = useQueryClient(); 9 | 10 | const { data, isLoading, isFetching } = useQuery({ 11 | queryKey: ['batches'], 12 | queryFn: () => SimpleGet(`/api/batch/active`) 13 | }); 14 | 15 | const refresh = () => { 16 | queryClient.invalidateQueries({ queryKey: ['batches'] }); 17 | }; 18 | 19 | 20 | 21 | return { data, isLoading: isLoading || isFetching, refresh }; 22 | } 23 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useBulkDownload.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; 2 | import {SimpleGet, SimplePost} from "../SimpleREST"; 3 | import {IBulkDownload, IBulkDownloadFile} from "./useBulkDownloads"; 4 | 5 | 6 | export function useBulkDownload(bulkDownloadId: number | string){ 7 | const queryClient = useQueryClient(); 8 | 9 | const {data, isLoading, isFetching} = useQuery({ 10 | queryKey: ['bulkDownload', {bulkDownloadId}] as [string, {bulkDownloadId: string | number}], 11 | queryFn: ({queryKey: [, {bulkDownloadId}]}) => { 12 | return SimpleGet(`/api/downloads/${bulkDownloadId}`) 13 | }, 14 | refetchInterval: 15000 15 | }) 16 | 17 | const refresh = ()=>{ 18 | queryClient.invalidateQueries({queryKey: ['bulkDownload', {bulkDownloadId}]}) 19 | queryClient.invalidateQueries({queryKey: ['bulkDownloads']}) 20 | } 21 | 22 | const mutation = useMutation({ 23 | mutationFn: ({command, bulkDownloadId, files}: {command: 'repeatFiles', bulkDownloadId: number, files?: Partial[]})=>{ 24 | switch(command){ 25 | case 'repeatFiles': return SimplePost(`/api/bulkDownloads/${bulkDownloadId}/bulkDownloadFiles`, files); 26 | } 27 | }, 28 | onSuccess: refresh 29 | }) 30 | 31 | 32 | return {data, isLoading: isLoading || isFetching, refresh, mutation} 33 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useBulkDownloadStatus.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { SimpleGet } from "../SimpleREST.ts"; 3 | import {IBulkDownloadFile} from "./useBulkDownloads.ts"; 4 | export function useBulkDownloadStatus({bulkDownloadId, status, count, skip}: {count?: number, skip?: number, bulkDownloadId: number | string, status: string}){ 5 | 6 | const {data, isLoading, isFetching, refetch} = useQuery({ 7 | queryKey: [`downloads/singleStatus`, {bulkDownloadId, status, count, skip}] as [string, {count?: number, skip?: number, bulkDownloadId: string | number, status: string}], 8 | queryFn: () => SimpleGet(`/api/downloads/${bulkDownloadId}/status/${status}`, {params: {count, skip}} ), 9 | cacheTime: 15000, 10 | initialData: [], 11 | refetchOnMount: true 12 | }) 13 | 14 | return {data, isLoading: isLoading || isFetching, refetch} 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useBulkDownloads.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; 2 | import {SimpleDelete, SimpleGet, SimplePatch, SimplePost} from "../SimpleREST"; 3 | import {IBulkUpload} from "./useBulkUploads"; 4 | 5 | export interface IBulkDownload { 6 | id: number, 7 | name: string, 8 | cloudPath: string, 9 | localPath: string, 10 | hubId: string, 11 | projectId: string, 12 | apsFolderId: string, 13 | logs?:string, 14 | status?:string, 15 | pendingDownloadCount?:number, 16 | inProgressDownloadCount?:number, 17 | successDownloadCount?:number, 18 | failedDownloadCount?:number, 19 | files?:IBulkDownloadFile[], 20 | createdAt: Date, 21 | } 22 | 23 | export interface IBulkDownloadFile { 24 | id: number, 25 | bulkDownloadId: number, 26 | fileName: string, 27 | sourceFilePath: string, 28 | destinationFilePath: string, 29 | itemId: string, 30 | fileSize: number, 31 | downloadFileStatus: string, 32 | status?:string, 33 | logs: string, 34 | createdOn: Date, 35 | lastModified: Date, 36 | } 37 | 38 | export function useBulkDownloads() { 39 | const queryClient = useQueryClient(); 40 | 41 | const {data, isLoading, isFetching} = useQuery({ 42 | queryKey: ['bulkDownloads'], 43 | queryFn: () => SimpleGet(`/api/bulkDownloads`), 44 | refetchInterval: 15000 45 | }) 46 | 47 | const refresh = ()=>{ 48 | queryClient.invalidateQueries({queryKey: ['bulkDownloads']}) 49 | } 50 | 51 | const mutation = useMutation({ 52 | mutationFn: ({command, bulkDownloads}: {command: 'patch' | 'post' | 'delete', bulkDownloads?: Partial[]})=>{ 53 | switch(command){ 54 | case 'post': return SimplePost(`/api/download`, bulkDownloads); 55 | // case 'patch': return SimplePatch(`/api/bulkUploads`, bulkUploads); 56 | case 'delete': return Promise.all(bulkDownloads?.map(bulkDownload=>SimpleDelete(`/api/download?id=${bulkDownload.id}`)) ?? []); 57 | default: return Promise.resolve(); 58 | } 59 | }, 60 | onSuccess: refresh 61 | }) 62 | 63 | return {data, isLoading: isLoading || isFetching, refresh, mutation} 64 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useBulkUpload.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; 2 | import {SimpleGet, SimplePost} from "../SimpleREST.ts"; 3 | import {IBulkUpload, IBulkUploadFile} from "./useBulkUploads.ts"; 4 | 5 | export function useBulkUpload(bulkUploadId: number | string){ 6 | const queryClient = useQueryClient(); 7 | 8 | const {data, isLoading, isFetching} = useQuery({ 9 | queryKey: ['bulkUpload', {bulkUploadId}] as [string, {bulkUploadId: string | number}], 10 | queryFn: ({queryKey: [, {bulkUploadId}]}) => { 11 | return SimpleGet(`/api/bulkUploads/${bulkUploadId}` ) 12 | }, 13 | refetchInterval: 15000 14 | }) 15 | 16 | const refresh = ()=>{ 17 | queryClient.invalidateQueries({queryKey: ['bulkUpload', {bulkUploadId}]}) 18 | queryClient.invalidateQueries({queryKey: ['bulkUploads']}) 19 | } 20 | 21 | const filesMutation = useMutation({ 22 | mutationFn: ({command, bulkUploadId, files}: {command: 'repeatFiles', bulkUploadId: number, files?: Partial[]})=>{ 23 | switch(command){ 24 | case 'repeatFiles': return SimplePost(`/api/bulkUploads/${bulkUploadId}/bulkUploadFiles`, files); 25 | } 26 | }, 27 | onSuccess: refresh 28 | }) 29 | 30 | return {data, isLoading: isLoading || isFetching, refresh, filesMutation} 31 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useBulkUploadPresets.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; 2 | import {SimpleDelete, SimpleGet, SimplePatch, SimplePost} from "../SimpleREST.ts"; 3 | 4 | export interface IBulkUploadPreset{ 5 | id: number; 6 | name: string; 7 | excludedFolderNames: string; 8 | excludedFileTypes: string; 9 | modifyPathScript: string; 10 | useModifyPathScript: boolean; 11 | } 12 | export function useBulkUploadPresets(options: any){ 13 | const queryClient = useQueryClient(); 14 | 15 | const {data, isLoading, isFetching} = useQuery({ 16 | ...options, 17 | queryKey: ['bulkUploadPresets'], 18 | queryFn: () => SimpleGet(`/api/settings/bulkUploadPresets` ) 19 | }) 20 | 21 | const refresh = ()=>{ 22 | queryClient.invalidateQueries({queryKey: ['bulkUploadPresets']}) 23 | } 24 | 25 | const mutation = useMutation({ 26 | mutationFn: ({command, presets}: {command: 'patch' | 'post' | 'delete', presets?: Partial[]})=>{ 27 | switch(command){ 28 | case 'post': return SimplePost(`/api/settings/bulkUploadPresets`, presets); 29 | case 'patch': return SimplePatch(`/api/settings/bulkUploadPresets`, presets); 30 | case 'delete': 31 | return Promise.all(presets?.map(preset=>SimpleDelete(`/api/settings/bulkUploadPresets/${preset.id}`)) ?? []) 32 | } 33 | }, 34 | onSuccess: refresh 35 | }) 36 | 37 | return {data, isLoading: isLoading || isFetching, refresh, presetMutation: mutation} 38 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useBulkUploadStatus.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { SimpleGet } from "../SimpleREST.ts"; 3 | import {IBulkUploadFile} from "./useBulkUploads.ts"; 4 | export function useBulkUploadStatus({bulkUploadId, status, count, skip}: {count?: number, skip?: number, bulkUploadId: number | string, status: string}){ 5 | 6 | const {data, isLoading, isFetching, refetch} = useQuery({ 7 | queryKey: [`bulkUploads/singleStatus`, {bulkUploadId, status, count, skip}] as [string, {count?: number, skip?: number, bulkUploadId: string | number, status: string}], 8 | queryFn: () => SimpleGet(`/api/bulkUploads/${bulkUploadId}/status/${status}`, {params: {count, skip}} ), 9 | cacheTime: 15000, 10 | initialData: [], 11 | refetchOnMount: true 12 | }) 13 | 14 | return {data, isLoading: isLoading || isFetching, refetch} 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useBulkUploads.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; 2 | import {SimpleDelete, SimpleGet, SimplePatch, SimplePost} from "../SimpleREST.ts"; 3 | 4 | export interface IBulkUpload { 5 | id: number; 6 | name: string; 7 | localPath: string; 8 | projectId: string; 9 | folderId: string; 10 | excludedFolderNames: string; 11 | excludedFileTypes: string; 12 | modifyPathScript: string; 13 | useModifyPathScript: boolean; 14 | startTime?: Date; 15 | endTime?: Date; 16 | status?: string; 17 | logs?: string; 18 | proposedFileCount?: number; 19 | doNotUploadFileCount?: number; 20 | pendingFileCount?: number; 21 | successFileCount?: number; 22 | failedFileCount?: number; 23 | files?: IBulkUploadFile[] 24 | autodeskMirrors?: IBulkUploadAutodeskMirror[] 25 | } 26 | 27 | export interface IBulkUploadAutodeskMirror{ 28 | folderName: string; 29 | folderUrl: string; 30 | folderUrn: string; 31 | relativeFolderPath: string; 32 | } 33 | 34 | export interface IBulkUploadFile{ 35 | id: number; 36 | bulkUploadId: number; 37 | sourceFileName: string; 38 | sourceAbsolutePath: string; 39 | sourceRelativePath: string; 40 | targetFileName: string; 41 | targetRelativePath: string; 42 | folderUrn: string; 43 | folderUrl: string; 44 | webUrl: string; 45 | versionId: string; 46 | status: string; 47 | logs: string; 48 | createdOn: Date; 49 | lastModified: Date; 50 | } 51 | 52 | export function useBulkUploads(){ 53 | const queryClient = useQueryClient(); 54 | 55 | const {data, isLoading, isFetching} = useQuery({ 56 | queryKey: ['bulkUploads'], 57 | queryFn: () => SimpleGet(`/api/bulkUploads` ), 58 | refetchInterval: 15000 59 | }) 60 | 61 | const refresh = ()=>{ 62 | queryClient.invalidateQueries({queryKey: ['bulkUploads']}) 63 | } 64 | 65 | const mutation = useMutation({ 66 | mutationFn: ({command, bulkUploads}: {command: 'patch' | 'post' | 'delete', bulkUploads?: Partial[]})=>{ 67 | switch(command){ 68 | case 'post': return SimplePost(`/api/bulkUploads`, bulkUploads); 69 | case 'patch': return SimplePatch(`/api/bulkUploads`, bulkUploads); 70 | case 'delete': return Promise.all(bulkUploads?.map(bulkUpload=>SimpleDelete(`/api/bulkUploads/${bulkUpload.id}`)) ?? []); 71 | } 72 | }, 73 | onSuccess: refresh 74 | }) 75 | 76 | return {data, isLoading: isLoading || isFetching, refresh, mutation} 77 | } 78 | 79 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useDataManagement.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; 2 | import {SimpleGet, SimplePatch} from "../SimpleREST.ts"; 3 | import {useMemo} from "react"; 4 | import {Hub} from "@mui/icons-material"; 5 | 6 | export interface ISimpleDmResponse { 7 | id: string; 8 | name: string; 9 | } 10 | 11 | export function useHubs(options: any) { 12 | return useQuery({ 13 | ...options, 14 | queryKey: ["hubs"], 15 | queryFn: () => SimpleGet(`/api/dm/hubs`) 16 | }); 17 | } 18 | export function useProjects(hubId: string | null, options: any) { 19 | return useQuery({ 20 | ...options, 21 | queryKey: ["projects", hubId], 22 | queryFn: () => SimpleGet(`/api/dm/projects?hubId=${hubId}`) 23 | }) 24 | } 25 | 26 | export function useTopFolders(hubId: string, projectId: string, options: any) { 27 | return useQuery({ 28 | ...options, 29 | queryKey: ["topFolders", hubId, projectId], 30 | queryFn: () => SimpleGet(`/api/dm/topFolders?hubId=${hubId}&projectId=${projectId}`) 31 | }) 32 | } 33 | 34 | export function useFolderContents(projectId: string, folderId: string, options: any) { 35 | return useQuery({ 36 | ...options, 37 | queryKey: ["folderContents", projectId, folderId], 38 | queryFn: () => SimpleGet(`/api/dm/folderContent?projectId=${projectId}&folderId=${folderId}`) 39 | }) 40 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useMessageListener.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from "react"; 2 | 3 | export function useMessageListener(type: string, callback: (key: string)=>void) { 4 | const [data, setData] = useState("") 5 | 6 | useEffect(() => { 7 | if (window) { 8 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 9 | //@ts-ignore 10 | window.chrome.webview.addEventListener('message', (event) => { 11 | try { 12 | const parsedData =event.data as {type: string, data: string}; 13 | if (parsedData.type === type) { 14 | setData(parsedData.data) 15 | if(callback) callback(parsedData.data); 16 | } 17 | } catch (e) { 18 | console.error(e); 19 | } 20 | }) 21 | } 22 | 23 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 24 | //@ts-ignore 25 | return window.chrome.webview.removeEventListener('message', ()=>{}) 26 | }, []) 27 | 28 | function sendMessage(message: string) { 29 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 30 | //@ts-ignore 31 | window.chrome.webview.postMessage(message); 32 | } 33 | 34 | return {data, sendMessage} 35 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useScripting.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {SimplePost} from "../SimpleREST.ts"; 3 | import {IBulkUpload, IBulkUploadFile} from "./useBulkUploads.ts"; 4 | 5 | export function useScripting(){ 6 | 7 | const mutation = useMutation({ 8 | mutationFn: ({command, bulkUpload, bulkUploadFile}: {command: 'post', bulkUpload: Partial, bulkUploadFile: Partial})=>{ 9 | switch(command){ 10 | case 'post': return SimplePost(`/api/scripting/modifyPath`, { 11 | bulkUpload, 12 | bulkUploadFile 13 | }); 14 | } 15 | } 16 | }) 17 | return {mutation} 18 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useSettings.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; 2 | import {SimpleGet, SimplePatch} from "../SimpleREST.ts"; 3 | import {useMemo} from "react"; 4 | 5 | export interface ISetting{ 6 | settingId: number; 7 | key: string; 8 | value: string; 9 | } 10 | export function useSettings(){ 11 | const queryClient = useQueryClient(); 12 | 13 | const {data, isLoading, isFetching} = useQuery({ 14 | queryKey: ['settings'], 15 | queryFn: () => SimpleGet(`/api/settings` ) 16 | }) 17 | 18 | const refresh = ()=>{ 19 | queryClient.invalidateQueries({queryKey: ['settings']}) 20 | } 21 | 22 | const mutation = useMutation({ 23 | mutationFn: ({command, settings}: {command: 'patch', settings?: ISetting[]})=>{ 24 | switch(command){ 25 | case 'patch': return SimplePatch(`/api/settings`, settings); 26 | } 27 | }, 28 | onSuccess: refresh 29 | }) 30 | 31 | const dataHash = useMemo(()=>{ 32 | return data?.reduce((acc: {[key: string]: ISetting}, val)=>({...acc, [val.key]: val}), {}) ?? {} 33 | }, [data]) 34 | 35 | return {data, dataHash, isLoading: isLoading || isFetching, refresh, mutation} 36 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/Hooks/useThreeLegged.ts: -------------------------------------------------------------------------------- 1 | import {SimpleGet, SimplePost, SimpleDelete} from "../SimpleREST.ts"; 2 | import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; 3 | 4 | //Test this in built application 5 | export function useIsAuthenticated() { 6 | const {data: isAuthenticated, isLoading: isAuthenticatedLoading, refetch} = useQuery({ 7 | queryKey: ['threeLegged/isAuthenticated'], 8 | queryFn: () => SimpleGet(`/api/settings/threeLegged/check`), 9 | refetchOnWindowFocus: "always" 10 | }); 11 | 12 | return {isAuthenticated, isLoading: isAuthenticatedLoading}; 13 | } 14 | export function useThreeLegged() { 15 | const queryClient = useQueryClient(); 16 | 17 | // const {data: isAuthenticated, isLoading: isAuthenticatedLoading} = useQuery({ 18 | // queryKey: ['threeLegged/isAuthenticated'], 19 | // queryFn: () => SimpleGet(`/api/settings/threeLegged/check`), 20 | // // refetchOnWindowFocus: "always" 21 | // }); 22 | 23 | 24 | const {data: authUrl, isLoading: isAuthUrlLoading} = useQuery({ 25 | queryKey: ['threeLegged/authUrl'], 26 | queryFn: () => SimpleGet(`/api/settings/threeLegged/getAuthUrl`), 27 | }); 28 | 29 | const refresh = () => { 30 | return Promise.all([ 31 | queryClient.invalidateQueries({queryKey: ['threeLegged/isAuthenticated']}), 32 | queryClient.invalidateQueries({queryKey: ['threeLegged/authUrl']}) 33 | ]); 34 | } 35 | const refreshAuthUrl = () => { 36 | return new Promise((resolve, reject) => { 37 | refresh().then(()=>{ 38 | setTimeout(resolve, 1000); 39 | }) 40 | }) 41 | } 42 | 43 | // const processCode = useMutation({ 44 | // mutationFn: ({code}: { code: string }) => { 45 | // debugger; 46 | // return SimplePost(`/api/settings/threeLegged`, {code}); 47 | // }, 48 | // onSuccess: refresh 49 | // }) 50 | 51 | const logout = useMutation({ 52 | mutationFn: () => { 53 | return SimpleDelete(`/api/settings/threeLegged`); 54 | }, 55 | onSuccess: refresh 56 | }) 57 | 58 | return { 59 | refresh: refreshAuthUrl, 60 | authUrl, 61 | // processCode, 62 | logout, 63 | isLoading: isAuthUrlLoading 64 | } 65 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/RenderTree.ts: -------------------------------------------------------------------------------- 1 | export interface IRenderTree { 2 | parentId: string | null; 3 | hubId: string | null; 4 | projectId: string | null; 5 | id: string; 6 | name: string; 7 | type: string | null; 8 | data: any; 9 | children: IRenderTree[]; 10 | isLoaded: boolean; 11 | isLoading: boolean; 12 | } 13 | 14 | export interface NodeVisitorFunction { 15 | (visitingNode: IRenderTree): boolean; 16 | } 17 | 18 | export class RenderTree { 19 | constructor(private root: IRenderTree) {} 20 | 21 | find(id: string): IRenderTree | undefined { 22 | let first; 23 | 24 | const callback = (node: IRenderTree) => { 25 | if (node.id === id) { 26 | first = node; 27 | return false; 28 | } 29 | 30 | return true; 31 | }; 32 | 33 | this.search(this.root, callback); 34 | 35 | return first; 36 | } 37 | 38 | search(node: IRenderTree, callback: NodeVisitorFunction): boolean { 39 | const len = node.children?.length; 40 | let keepGoing = callback(node); 41 | 42 | for (let i = 0; i < len; i++) { 43 | if (keepGoing === false) { 44 | return false; 45 | } 46 | 47 | keepGoing = this.search(node.children[i], callback); 48 | } 49 | 50 | return keepGoing; 51 | } 52 | 53 | searchPath( 54 | node: IRenderTree, 55 | stack: IRenderTree[], 56 | callback: NodeVisitorFunction 57 | ): boolean { 58 | stack.push(node); 59 | 60 | const len = node.children?.length; 61 | let found = callback(node); 62 | 63 | for (let i = 0; i < len; i++) { 64 | if (found) { 65 | return true; 66 | } 67 | 68 | found = this.searchPath(node.children[i], stack, callback); 69 | } 70 | 71 | if (!found) { 72 | stack.pop(); 73 | } 74 | 75 | return found; 76 | } 77 | 78 | path(target: IRenderTree): IRenderTree[] { 79 | let stack: IRenderTree[] = []; 80 | 81 | const callback = (node: IRenderTree) => { 82 | return (node.id === target.id); 83 | }; 84 | 85 | this.searchPath(this.root, stack, callback); 86 | 87 | return stack; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/Utilities/SimpleREST.ts: -------------------------------------------------------------------------------- 1 | import axios, {AxiosRequestConfig} from "axios"; 2 | 3 | export function SimplePost(url: string, body?: any, options?: any): Promise { 4 | return new Promise((resolve, reject) => { 5 | axios.post(url, body, options) 6 | .then(result => { 7 | resolve(result?.data) 8 | }) 9 | .catch(error => { 10 | if(error.response?.status !== 401){ 11 | 12 | } 13 | reject(error); 14 | }) 15 | }) 16 | } 17 | 18 | export function SimplePut(url: string, body?: any, options?: any): Promise { 19 | return new Promise((resolve, reject) => { 20 | axios.put(url, body, options) 21 | .then(result => { 22 | resolve(result?.data) 23 | }) 24 | .catch(error => { 25 | if(error.response?.status !== 401){ 26 | 27 | } 28 | reject(error); 29 | }) 30 | }) 31 | } 32 | 33 | export function SimplePatch(url: string, body?: any, options?: any): Promise { 34 | return new Promise((resolve, reject) => { 35 | axios.patch(url, body, options) 36 | .then(result => { 37 | resolve(result?.data) 38 | }) 39 | .catch(error => { 40 | console.error(error) 41 | if(error.response?.status !== 401){ 42 | 43 | } 44 | reject(error); 45 | }) 46 | }) 47 | } 48 | 49 | export function SimpleDelete(url: string, options?: any): Promise { 50 | return new Promise((resolve, reject) => { 51 | axios.delete(url, options) 52 | .then(result => { 53 | resolve(result?.data) 54 | }) 55 | .catch(error => { 56 | 57 | reject(error); 58 | }) 59 | }) 60 | } 61 | 62 | export function SimpleGet(url: string, options?: AxiosRequestConfig): Promise{ 63 | return new Promise((resolve, reject) => { 64 | axios.get(url, options) 65 | .then(result => { 66 | resolve(result?.data as T) 67 | }) 68 | .catch(error => { 69 | if(error.response?.status !== 401){ 70 | 71 | } 72 | reject(error); 73 | }) 74 | }) 75 | } 76 | 77 | export function SimpleDownload(url: string, options?: any): Promise{ 78 | return new Promise((resolve, reject) => { 79 | axios.get(url, {responseType: 'blob', ...options}) 80 | .then(result => { 81 | resolve(result?.data) 82 | }) 83 | .catch(error => { 84 | if(error.response?.status !== 401){ 85 | 86 | } 87 | reject(error); 88 | }) 89 | }) 90 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: white /*#242424*/; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | /*a {*/ 18 | /* font-weight: 500;*/ 19 | /* color: #646cff;*/ 20 | /* text-decoration: inherit;*/ 21 | /*}*/ 22 | /*a:hover {*/ 23 | /* color: #535bf2;*/ 24 | /*}*/ 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | min-width: 100vw; 30 | min-height: 100vh; 31 | } 32 | 33 | /*h1 {*/ 34 | /* font-size: 3.2em;*/ 35 | /* line-height: 1.1;*/ 36 | /*}*/ 37 | 38 | /*button {*/ 39 | /* border-radius: 8px;*/ 40 | /* border: 1px solid transparent;*/ 41 | /* padding: 0.6em 1.2em;*/ 42 | /* font-size: 1em;*/ 43 | /* font-weight: 500;*/ 44 | /* font-family: inherit;*/ 45 | /* background-color: #1a1a1a;*/ 46 | /* cursor: pointer;*/ 47 | /* transition: border-color 0.25s;*/ 48 | /*}*/ 49 | /*button:hover {*/ 50 | /* border-color: #646cff;*/ 51 | /*}*/ 52 | /*button:focus,*/ 53 | /*button:focus-visible {*/ 54 | /* outline: 4px auto -webkit-focus-ring-color;*/ 55 | /*}*/ 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; 6 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools' 7 | import {BrowserRouter} from "react-router-dom"; 8 | 9 | const queryClient = new QueryClient({ 10 | defaultOptions: { 11 | queries: { 12 | refetchOnWindowFocus: false, 13 | refetchOnMount: false, 14 | } 15 | } 16 | }) 17 | 18 | ReactDOM.createRoot(document.getElementById('root')!).render( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | , 27 | ) 28 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": false, 20 | "noUnusedParameters": false, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/ClientApp/vite.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import mkcert from "vite-plugin-mkcert"; 4 | 5 | // https://vitejs.dev/config/ 6 | 7 | export default defineConfig({ 8 | plugins: [react(), mkcert()] 9 | , server: { 10 | port: 3000, 11 | https: true, 12 | proxy: { 13 | '/api': { 14 | target: 'http://localhost:8083', 15 | changeOrigin: true, 16 | secure: false, 17 | rewrite: (path) => path.replace(/^\/api/, '/api') 18 | }, 19 | '/hangfire': { 20 | target: 'http://localhost:8083', 21 | changeOrigin: true, 22 | secure: false, 23 | rewrite: (path) => path.replace(/^\/hangfire/, '/hangfire') 24 | }, 25 | '/code': { 26 | target: 'http://localhost:8083', 27 | changeOrigin: true, 28 | secure: false, 29 | rewrite: (path) => path.replace(/^\/code/, '/code') 30 | } 31 | } 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Controllers/BulkUploadPresetController.cs: -------------------------------------------------------------------------------- 1 | using ApsSettings.Data; 2 | using ApsSettings.Data.Models; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Bulk_Uploader_Electron.Controllers; 7 | 8 | public class BulkUploadPresetController: ControllerBase 9 | { 10 | private readonly DataContext _dataContext; 11 | 12 | public BulkUploadPresetController(DataContext dataContext) 13 | { 14 | _dataContext = dataContext; 15 | } 16 | 17 | [HttpGet] 18 | [Route("api/settings/bulkUploadPresets")] 19 | public async Task GetSettings() 20 | { 21 | var presets = await _dataContext.BulkUploadPresets.ToListAsync(); 22 | return Ok(presets); 23 | } 24 | 25 | [HttpPatch] 26 | [Route("api/setting/bulkUploadPresets")] 27 | public async Task PatchSettings([FromBody] List presets ) 28 | { 29 | _dataContext.BulkUploadPresets.UpdateRange(presets); 30 | await _dataContext.SaveChangesAsync(); 31 | return Ok(); 32 | } 33 | 34 | [HttpPost] 35 | [Route("api/settings/bulkUploadPresets")] 36 | public async Task PostSettings([FromBody] List presets ) 37 | { 38 | await _dataContext.BulkUploadPresets.AddRangeAsync(presets); 39 | await _dataContext.SaveChangesAsync(); 40 | return Created("api/settings/bulkUploadPresets", presets); 41 | } 42 | 43 | [HttpDelete] 44 | [Route("api/settings/bulkUploadPresets/{id}")] 45 | public async Task DeleteSettings(int id) 46 | { 47 | var preset = await _dataContext.BulkUploadPresets.FirstOrDefaultAsync(x => x.Id == id); 48 | if (preset == null) return NotFound(); 49 | 50 | _dataContext.BulkUploadPresets.Remove(preset); 51 | await _dataContext.SaveChangesAsync(); 52 | return NoContent(); 53 | } 54 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Controllers/ScriptingController.cs: -------------------------------------------------------------------------------- 1 | using ApsSettings.Data.Models; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.ClearScript.V8; 4 | 5 | namespace Bulk_Uploader_Electron.Controllers; 6 | 7 | public class ModifyPathDTU 8 | { 9 | public BulkUpload BulkUpload { get; set; } 10 | public BulkUploadFile BulkUploadFile { get; set; } 11 | } 12 | 13 | public class ScriptingController: ControllerBase 14 | { 15 | [HttpPost] 16 | [Route("api/scripting/modifyPath")] 17 | public ActionResult ModifyPath([FromBody] ModifyPathDTU dtu) 18 | { 19 | try 20 | { 21 | var newFile = BulkUploadFile.CreateFile( 22 | dtu.BulkUpload, 23 | dtu.BulkUploadFile.SourceRelativePath, 24 | dtu.BulkUploadFile.SourceAbsolutePath 25 | ); 26 | 27 | return Ok(newFile); 28 | } 29 | catch (Exception e) 30 | { 31 | return BadRequest(e.Message); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Helpers/APSClientHelper.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Authentication; 2 | using Autodesk.DataManagement; 3 | using Autodesk.Oss.Http; 4 | using Autodesk.SDKManager; 5 | 6 | namespace Bulk_Uploader_Electron.Helpers 7 | { 8 | internal class APSClientHelper 9 | { 10 | internal static SDKManager SdkManager => SdkManagerBuilder.Create().Build(); 11 | internal static AuthenticationClient AuthClient => new (SdkManager); 12 | internal static DataManagementClient DataManagement => new(SdkManager); 13 | internal static OSSApi OssApi => new(SdkManager); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Helpers/FileMetricsHelper.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | 4 | namespace Bulk_Uploader_Electron.Helpers 5 | { 6 | public class FileCollectionMetrics 7 | { 8 | public string Path = ""; 9 | public float TotalSizeMb = 0; 10 | public int TotalFileCount = 0; 11 | } 12 | public class FileMetricsHelper 13 | { 14 | /// 15 | /// Given a path, this will iterate all files recursively for a total file size (MB) and file count 16 | /// 17 | /// 18 | /// 19 | public FileCollectionMetrics GetFileSizeAndCount(string rootFolderPath) 20 | { 21 | 22 | var metrics = new FileCollectionMetrics(); 23 | 24 | try 25 | { 26 | metrics.Path = rootFolderPath; 27 | 28 | // Get full exclusion list 29 | string[] customerExclusion = AppSettings.Instance.CustomerExcludedFileTypes.Split(","); 30 | string[] folderExclusion = AppSettings.Instance.CustomerExcludedFolderNames.Split(","); 31 | var accExclusion = AppSettings.Instance.IllegalFileTypes; //Known ACC 32 | accExclusion = accExclusion.Union(customerExclusion).ToArray(); 33 | 34 | accExclusion = accExclusion.Select(s => s.ToUpperInvariant()).ToArray(); 35 | 36 | var localFiles = Directory 37 | .GetFiles(rootFolderPath, "*", SearchOption.AllDirectories) 38 | .ToList(); 39 | 40 | long filesInBytes = 0; 41 | int fileCount = 0; 42 | 43 | foreach (string filename in localFiles) 44 | { 45 | foreach (var fold in folderExclusion) 46 | { 47 | var folders = filename.Split(Path.DirectorySeparatorChar); 48 | if (!folders.Any(x => x.ToUpper() == fold.ToUpper())) 49 | { 50 | string extension = Path.GetExtension(filename) 51 | .ToUpper() 52 | .Replace(".", ""); 53 | 54 | if (!accExclusion.Contains(extension)) 55 | { 56 | fileCount++; 57 | filesInBytes += new FileInfo(filename).Length; 58 | } 59 | } 60 | } 61 | } 62 | 63 | //Convert bytes to Mega 64 | var filesInMb = (filesInBytes / 1024f) / 1024f; 65 | metrics.TotalSizeMb = filesInMb; 66 | metrics.TotalFileCount = fileCount; 67 | } 68 | catch (Exception ex) 69 | { 70 | Log.Error("Problem calculating file sizes and count for local files"); 71 | } 72 | 73 | return metrics; 74 | } 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Helpers/FolderHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Bulk_Uploader_Electron.Helpers 4 | { 5 | public class FolderHelpers 6 | { 7 | public static bool PathHasFolder(string pathToFileName, string folderToCheck) 8 | { 9 | bool bHasFolder = false; 10 | 11 | try 12 | { 13 | var folders = pathToFileName.Split(Path.DirectorySeparatorChar); 14 | bHasFolder = folders.Any(x => x.ToUpper() == folderToCheck.ToUpper()); 15 | } 16 | catch (Exception ex) 17 | { 18 | } 19 | 20 | return bHasFolder; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Helpers/JointTokenManager.cs: -------------------------------------------------------------------------------- 1 | using Ac.Net.Authentication; 2 | using Bulk_Uploader_Electron.Managers; 3 | 4 | namespace Bulk_Uploader_Electron.Helpers; 5 | 6 | public static class JointTokenManager 7 | { 8 | public static async Task GetToken() 9 | { 10 | try 11 | { 12 | return await ThreeLeggedTokenManager.Instance.GetToken(); 13 | } 14 | catch (Exception) 15 | { 16 | return await TwoLeggedTokenManager.GetTwoLeggedToken(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Helpers/TwoLeggedTokenManager.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Authentication.Model; 2 | using Bulk_Uploader_Electron.Helpers; 3 | 4 | namespace Bulk_Uploader_Electron.Managers 5 | { 6 | public static class TwoLeggedTokenManager 7 | { 8 | #region Properties 9 | private static string TwoLeggedToken { get; set; } = string.Empty; 10 | private static DateTime TwoLeggedTokenExpiration { get; set; } 11 | private static string? ClientId { get; set; } 12 | private static string? ClientSecret { get; set; } 13 | #endregion 14 | 15 | 16 | #region Methods 17 | public static async Task GetTwoLeggedToken() 18 | { 19 | if (ClientId != AppSettings.Instance.ClientId || ClientSecret != AppSettings.Instance.ClientSecret) 20 | { 21 | ClientId = AppSettings.Instance.ClientId; 22 | ClientSecret = AppSettings.Instance.ClientSecret; 23 | return await RequestTwoLeggedToken(); 24 | } 25 | 26 | if (!string.IsNullOrEmpty(TwoLeggedToken) && TwoLeggedTokenExpiration > (DateTime.UtcNow.AddMinutes(15))) 27 | return TwoLeggedToken; 28 | else 29 | return await RequestTwoLeggedToken(); 30 | } 31 | private static async Task RequestTwoLeggedToken() 32 | { 33 | var twoLeggedToken = await APSClientHelper.AuthClient.GetTwoLeggedTokenAsync(AppSettings.Instance.ClientId, AppSettings.Instance.ClientSecret, AppSettings.Instance.ForgeTwoLegScope); 34 | 35 | TwoLeggedToken = twoLeggedToken.AccessToken; 36 | TwoLeggedTokenExpiration = DateTime.UtcNow.AddSeconds(twoLeggedToken.ExpiresIn ?? 60); 37 | 38 | return TwoLeggedToken; 39 | } 40 | #endregion 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Models/Account.cs: -------------------------------------------------------------------------------- 1 |  2 | using Ganss.Excel; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace Bulk_Uploader_Electron.Models 7 | { 8 | public class Account 9 | { 10 | [Key] 11 | [JsonPropertyName("id")] public int Id { get; set; } 12 | 13 | [Column("Account ID")] 14 | [JsonPropertyName("accountId")] 15 | public string AccountId { get; set; } 16 | 17 | [Column("Account Name")] 18 | [JsonPropertyName("name")] 19 | public string Name { get; set; } 20 | 21 | 22 | [Column("Account Enabled")] 23 | [JsonPropertyName("enabled")] 24 | public bool Enabled { get; set; } 25 | 26 | [Column("Account Region")] 27 | [JsonPropertyName("region")] 28 | public string Region { get; set; } 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Models/BusinessUnit.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Bulk_Uploader_Electron.Models 5 | { 6 | public class BusinessUnit 7 | { 8 | [Key] 9 | [JsonPropertyName("id")] public int Id { get; set; } 10 | 11 | [JsonPropertyName("accountId")] public string AccountId { get; set; } 12 | [JsonPropertyName("businessUnitId")] public string BusinessUnitId { get; set; } 13 | [JsonPropertyName("name")] public string Name { get; set; } 14 | [JsonPropertyName("path")] public string Path { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Models/ErrorMessage.cs: -------------------------------------------------------------------------------- 1 |  2 | using Ganss.Excel; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Bulk_Uploader_Electron.Models 10 | { 11 | public class ErrorMessage 12 | { 13 | public ErrorMessage(string heading, string data, string details) 14 | { 15 | Heading = heading; 16 | Data = data; 17 | Details = details; 18 | } 19 | 20 | public ErrorMessage(string heading, Exception ex) 21 | { 22 | Heading = heading; 23 | Data = ex.Message; 24 | Details = ex.StackTrace?? "No Stack"; 25 | } 26 | 27 | [Column("Error")] public string Heading { get; } 28 | [Column("Message")] public string Data { get; } 29 | [Column("Details")] public string Details { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Models/Project.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | using Bulk_Uploader_Electron.Managers; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Converters; 10 | 11 | namespace Bulk_Uploader_Electron.Models 12 | { 13 | public class Project 14 | { 15 | [Key] 16 | [JsonPropertyName("id")] public int Id { get; set; } 17 | 18 | [Column("BU ID")] 19 | [JsonPropertyName("businessUnitId")] 20 | public string BusinessUnitId { get; set; } 21 | 22 | [Column("Account ID")] 23 | [JsonPropertyName("accountId")] public string 24 | AccountId { get; set; } 25 | 26 | [Column("Project ID")] 27 | [JsonPropertyName("projectId")] 28 | public string ProjectId { get; set; } 29 | 30 | [Column("Project Name")] 31 | [JsonPropertyName("name")] 32 | public string Name { get; set; } 33 | 34 | [Column("Project Status")] 35 | [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))] 36 | [JsonPropertyName("projectType")] 37 | public ProjectType ProjectType { get; set; } 38 | [JsonPropertyName("status")] public bool Status { get; set; } 39 | 40 | [Column("Project Wieght")] 41 | [JsonPropertyName("weight")] 42 | public int Weight { get; set; } 43 | } 44 | 45 | public enum ProjectType 46 | { 47 | ACC, 48 | BIM360 49 | } 50 | 51 | 52 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Models/SimpleFile.cs: -------------------------------------------------------------------------------- 1 | using Ganss.Excel; 2 | 3 | namespace Bulk_Uploader_Electron.Models 4 | { 5 | public interface ISimpleFile 6 | { 7 | string Name { get; set; } 8 | string Path { get; set; } 9 | } 10 | 11 | public class SimpleFile : ISimpleFile 12 | { 13 | [Column("Item ID")] 14 | public string ItemId { get; set; } 15 | [Column("Version ID")] 16 | public string VersionId { get; set; } 17 | [Column("Derivative ID")] 18 | public string DerivativeId { get; set; } 19 | [Column("Storage ID")] 20 | public string ObjectId { get; set; } 21 | [Column("Name")] 22 | public string Name { get; set; } 23 | [Column("File Type")] 24 | public string FileType { get; set; } 25 | [Column("Item URL")] 26 | public string Url { get; set; } 27 | [Column("Last Modified")] 28 | public DateTime LastModified { get; set; } 29 | [Column("Parent")] 30 | public string? ParentPath { get; set; } 31 | [Column("Path")] 32 | public string? Path { get; set; } 33 | [Column("Size")] 34 | public long Size { get; set; } = 0; 35 | 36 | [Ignore] 37 | public List CustomAttributes { get; set; } = new List(); 38 | } 39 | 40 | public class CustomAttribute 41 | { 42 | public int id { get; set; } 43 | public string type { get; set; } 44 | public string name { get; set; } 45 | public string value { get; set; } 46 | } 47 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Models/SimpleFolder.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using Ganss.Excel; 4 | 5 | namespace Bulk_Uploader_Electron.Models 6 | { 7 | public class SimpleFolder 8 | { 9 | [Column("Folder ID")] 10 | public string FolderId { get; set; } 11 | [Column("Folder Name")] 12 | public string Name { get; set; } 13 | [Column("Folder URL")] 14 | public string Url { get; set; } 15 | [Column("Parent")] 16 | public string? ParentPath { get; set; } 17 | [Column("Path")] 18 | public string? Path { get; set; } 19 | [Column("Is Root")] 20 | public bool IsRoot { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Bulk_Uploader_Electron 2 | { 3 | public class Program 4 | { 5 | [STAThread] 6 | public static void Main(string[] args) 7 | { 8 | CreateHostBuilder(args).Build().RunAsync(); 9 | 10 | Application.SetHighDpiMode(HighDpiMode.SystemAware); 11 | Application.EnableVisualStyles(); 12 | Application.SetCompatibleTextRenderingDefault(false); 13 | Application.Run(new MainForm()); 14 | 15 | //Handle random errors without crashing the application 16 | AppDomain.CurrentDomain.FirstChanceException += (sender, e) => 17 | { 18 | if ((e == null) || (e.Exception == null)) 19 | { 20 | return; 21 | } 22 | 23 | using (var sw = File.AppendText(@".\exceptions.txt")) 24 | { 25 | sw.WriteLine(e.Exception); 26 | } 27 | }; 28 | 29 | AppDomain.CurrentDomain.UnhandledException += (sender, e) => 30 | { 31 | if ((e == null) || (e.ExceptionObject == null)) 32 | { 33 | return; 34 | } 35 | 36 | using (var sw = File.AppendText(@".\exceptions.txt")) 37 | { 38 | sw.WriteLine(e.ExceptionObject); 39 | } 40 | }; 41 | } 42 | 43 | 44 | public static IHostBuilder CreateHostBuilder(string[] args) => 45 | Host.CreateDefaultBuilder(args) 46 | .ConfigureWebHostDefaults(webBuilder => 47 | { 48 | 49 | // webBuilder.UseElectron(args); 50 | webBuilder.UseStartup(); 51 | 52 | webBuilder.UseUrls("http://localhost:8083"); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:22876", 7 | "sslPort": 44338 8 | } 9 | }, 10 | "profiles": { 11 | "Electron.NET App": { 12 | "commandName": "Executable", 13 | "executablePath": "electronize", 14 | "commandLineArgs": "start", 15 | "workingDirectory": "." 16 | }, 17 | 18 | "Bulk_Uploader_Electron": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": true, 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development", 24 | "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" 25 | } 26 | }, 27 | "IIS Express": { 28 | "commandName": "IISExpress", 29 | "launchBrowser": true, 30 | "environmentVariables": { 31 | "ASPNETCORE_ENVIRONMENT": "Development" 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Resources/autodesk_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Bulk-Uploader-FrontEnd/Resources/autodesk_icon.ico -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Utils/BatchJobController.cs: -------------------------------------------------------------------------------- 1 | namespace Bulk_Uploader_Electron.ClientApp.src.Utilities 2 | { 3 | public class BatchJobController 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Utils/BatchManager.cs: -------------------------------------------------------------------------------- 1 | using PluginBase.Models; 2 | using Serilog; 3 | using System.Collections.Concurrent; 4 | using System.Reflection; 5 | 6 | namespace Bulk_Uploader_Electron.ClientApp.src.Utilities 7 | { 8 | public class BatchManager 9 | { 10 | public static readonly BatchManager Instance = new BatchManager(); 11 | 12 | private readonly ConcurrentDictionary batches = new System.Collections.Concurrent.ConcurrentDictionary(); 13 | 14 | public void RegisterAssembly(Assembly assembly) 15 | { 16 | foreach (Type type in assembly.GetTypes()) 17 | { 18 | if (typeof(IBatchRegistry).IsAssignableFrom(type)) 19 | { 20 | IBatchRegistry result = (Activator.CreateInstance(type) as IBatchRegistry)!; 21 | if (result != null) 22 | { 23 | try 24 | { 25 | RegisterBatch(result); 26 | } 27 | catch (Exception ex) 28 | { 29 | Log.Error("BatchManager.RegisterAssembly", ex); 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | public IBatchRegistry? GetBatch(string key) 37 | { 38 | return batches.ContainsKey(key) ? batches[key] : null; 39 | } 40 | 41 | public void RegisterBatch(IBatchRegistry batch) 42 | { 43 | var name = batch.BatchDetails.Key; 44 | if (string.IsNullOrEmpty(name)) throw new NullReferenceException("Batch key cannot be null"); 45 | if (batches.ContainsKey(name)) 46 | { 47 | throw new Exception($"{name} Batch already registered"); 48 | } 49 | if (!batches.TryAdd(name, batch)) 50 | { 51 | throw new Exception($"{name} Batch could not be added to batch registry"); 52 | } 53 | } 54 | 55 | public IBatchRegistry[] Batches 56 | { 57 | get => batches.Values.ToArray(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Utils/BatchRegistry.cs: -------------------------------------------------------------------------------- 1 | namespace Bulk_Uploader_Electron.ClientApp.src.Utilities 2 | { 3 | 4 | 5 | //public class BatchRegistry : IBatchRegistry 6 | //{ 7 | // //public HfBatchOperation BatchOperation { get; } 8 | // //public HfBatchTemplate GetTemplate { get; } 9 | // //public HfBatchLoad LoadBatch { get; } 10 | // //public HfBatchBuildStack BuildStack { get; } 11 | 12 | // //public BatchRegistry(HfBatchOperation batchOperation, HfBatchTemplate getTemplate, HfBatchLoad loadBatch, HfBatchBuildStack buildStack) 13 | // //{ 14 | // // BatchOperation = batchOperation; 15 | // // GetTemplate = getTemplate; 16 | // // LoadBatch = loadBatch; 17 | // // BuildStack = buildStack; 18 | // //} 19 | //} 20 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Utils/MappingProfiles.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Bulk_Uploader_Electron.Models; 3 | 4 | namespace Bulk_Uploader_Electron.Utils 5 | { 6 | public static class ClassMappings 7 | { 8 | public static readonly IMapper Mapper; 9 | 10 | 11 | /// 12 | /// Configures a static instance of a mapper 13 | /// 14 | static ClassMappings() 15 | { 16 | MapperConfiguration config = new MapperConfiguration(cfg => cfg.AddProfile()); 17 | Mapper = config.CreateMapper(); 18 | } 19 | } 20 | 21 | /// 22 | /// Defines set of Automapper profiles 23 | /// 24 | public class MigrationProfiles : Profile 25 | { 26 | public MigrationProfiles() 27 | { 28 | // CreateMap().ReverseMap(); 29 | 30 | // Use CreateMap... Etc.. here (Profile methods are the same as configuration methods) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Utils/PluginManager.cs: -------------------------------------------------------------------------------- 1 | using PluginBase; 2 | using PluginBase.Models; 3 | using Serilog; 4 | using System.Collections.Concurrent; 5 | using System.Reflection; 6 | 7 | namespace Bulk_Uploader_Electron.Utils 8 | { 9 | public class PluginManager 10 | { 11 | public static readonly PluginManager Instance = new PluginManager(); 12 | 13 | private ConcurrentDictionary plugins = new System.Collections.Concurrent.ConcurrentDictionary(); 14 | 15 | private ConcurrentDictionary globalSettings = new System.Collections.Concurrent.ConcurrentDictionary(); 16 | 17 | public void RegisterAssembly(Assembly assembly) 18 | { 19 | foreach (Type type in assembly.GetTypes()) 20 | { 21 | if (typeof(IJobExecutionPlugin).IsAssignableFrom(type)) 22 | { 23 | IJobExecutionPlugin result = (Activator.CreateInstance(type) as IJobExecutionPlugin)!; 24 | if (result != null) 25 | { 26 | try 27 | { 28 | RegisterPlugin(result); 29 | } 30 | catch (Exception ex) 31 | { 32 | Log.Error("PluginManager.RegisterAssembly", ex); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | public void RegisterPlugin(IJobExecutionPlugin plugin) 40 | { 41 | var name = plugin.GetType().Name; 42 | if (plugins.ContainsKey(name)) 43 | { 44 | throw new Exception($"{name} Plugin already registered"); 45 | } 46 | if (!plugins.TryAdd(plugin.GetType().Name, plugin)) 47 | { 48 | throw new Exception($"{name} Plugin could not be added to plugin registry"); 49 | } 50 | } 51 | 52 | public void RegisterGlobalSetting(string setting) 53 | { 54 | if (globalSettings.ContainsKey(setting)) 55 | { 56 | throw new Exception($"Setting '{setting}' is already registered"); 57 | } 58 | if (!globalSettings.TryAdd(setting, setting)) 59 | { 60 | throw new Exception($"Setting {setting} could not be added to global settings registry"); 61 | } 62 | } 63 | 64 | public IJobExecutionPlugin[] Plugins 65 | { 66 | get => plugins.Values.ToArray(); 67 | } 68 | 69 | public string[] GlobalSettings 70 | { 71 | get => globalSettings.Values.ToArray(); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/Views/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace Bulk_Uploader_Electron { 11 | using System; 12 | 13 | partial class MainForm 14 | { 15 | /// 16 | /// Required designer variable. 17 | /// 18 | private System.ComponentModel.IContainer components = null; 19 | /// 20 | /// Clean up any resources being used. 21 | /// 22 | /// true if managed resources should be disposed; otherwise, false. 23 | protected override void Dispose(bool disposing) 24 | { 25 | if (disposing && (components != null)) 26 | { 27 | components.Dispose(); 28 | } 29 | base.Dispose(disposing); 30 | } 31 | #region Windows Form Designer generated code 32 | /// 33 | /// Required method for Designer support - do not modify 34 | /// the contents of this method with the code editor. 35 | /// 36 | private void InitializeComponent() 37 | { 38 | this.webView21 = new Microsoft.Web.WebView2.WinForms.WebView2(); 39 | ((System.ComponentModel.ISupportInitialize)(this.webView21)).BeginInit(); 40 | this.SuspendLayout(); 41 | // 42 | // webView21 43 | // 44 | this.webView21.CreationProperties = null; 45 | this.webView21.DefaultBackgroundColor = System.Drawing.Color.White; 46 | this.webView21.Dock = System.Windows.Forms.DockStyle.Fill; 47 | this.webView21.Location = new System.Drawing.Point(0, 0); 48 | this.webView21.Name = "webView21"; 49 | this.webView21.Size = new System.Drawing.Size(1280, 768); 50 | this.webView21.Source = new System.Uri("http://localhost:8083/", System.UriKind.Absolute); 51 | this.webView21.TabIndex = 0; 52 | this.webView21.ZoomFactor = 1D; 53 | // 54 | // Form1 55 | // 56 | this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F); 57 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 58 | this.ClientSize = new System.Drawing.Size(1280, 768); 59 | this.Controls.Add(this.webView21); 60 | this.Name = "MainForm"; 61 | this.Text = "Autodesk Bulk File Manager"; 62 | this.ShowIcon = false; 63 | ((System.ComponentModel.ISupportInitialize)(this.webView21)).EndInit(); 64 | this.ResumeLayout(false); 65 | } 66 | #endregion 67 | public Microsoft.Web.WebView2.WinForms.WebView2 webView21; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/electron.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "executable": "BulkUploaderElectron", 3 | "splashscreen": { 4 | "imageFile": "" 5 | }, 6 | "name": "BulkUploaderElectron", 7 | "author": "", 8 | "singleInstance": false, 9 | "environment": "Production", 10 | "build": { 11 | "appId": "com.BulkUploaderElectron.app", 12 | "productName": "BulkUploaderElectron", 13 | "copyright": "Copyright © 2020", 14 | "buildVersion": "1.0.0", 15 | "compression": "maximum", 16 | "directories": { 17 | "output": "../../../bin/Desktop" 18 | }, 19 | "extraResources": [ 20 | { 21 | "from": "./bin", 22 | "to": "bin", 23 | "filter": [ "**/*" ] 24 | } 25 | ], 26 | "files": [ 27 | { 28 | "from": "./ElectronHostHook/node_modules", 29 | "to": "ElectronHostHook/node_modules", 30 | "filter": [ "**/*" ] 31 | }, 32 | "**/*" 33 | ] 34 | } 35 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* html { 2 | font-size: 36px; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | html { 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | html { 12 | position: relative; 13 | min-height: 100%; 14 | } 15 | 16 | body { 17 | margin-bottom: 60px; 18 | } */ 19 | body { 20 | padding: 2em; 21 | } -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Bulk-Uploader-FrontEnd/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2021 Twitter, Inc. 4 | Copyright (c) 2011-2021 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Bulk-Uploader-FrontEnd/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /BulkUploaderUtils/BulkUploaderUtils.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Helpers/FileMetricsHelper.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | 4 | namespace BulkUploaderUtils.Helpers 5 | { 6 | public class FileCollectionMetrics 7 | { 8 | public string Path = ""; 9 | public float TotalSizeMb = 0; 10 | public int TotalFileCount = 0; 11 | } 12 | public class FileMetricsHelper 13 | { 14 | /// 15 | /// Given a path, this will iterate all files recursively for a total file size (MB) and file count 16 | /// 17 | /// 18 | /// 19 | public FileCollectionMetrics GetFileSizeAndCount(string rootFolderPath) 20 | { 21 | 22 | var metrics = new FileCollectionMetrics(); 23 | 24 | try 25 | { 26 | metrics.Path = rootFolderPath; 27 | 28 | // Get full exclusion list 29 | string[] customerExclusion = AppSettings.Instance.CustomerExcludedFileTypes.Split(","); 30 | string[] folderExclusion = AppSettings.Instance.CustomerExcludedFolderNames.Split(","); 31 | var accExclusion = AppSettings.Instance.IllegalFileTypes; //Known ACC 32 | accExclusion = accExclusion.Union(customerExclusion).ToArray(); 33 | 34 | accExclusion = accExclusion.Select(s => s.ToUpperInvariant()).ToArray(); 35 | 36 | var localFiles = Directory 37 | .GetFiles(rootFolderPath, "*", SearchOption.AllDirectories) 38 | .ToList(); 39 | 40 | long filesInBytes = 0; 41 | int fileCount = 0; 42 | 43 | foreach (string filename in localFiles) 44 | { 45 | foreach (var fold in folderExclusion) 46 | { 47 | var folders = filename.Split(Path.DirectorySeparatorChar); 48 | if (!folders.Any(x => x.ToUpper() == fold.ToUpper())) 49 | { 50 | string extension = Path.GetExtension(filename) 51 | .ToUpper() 52 | .Replace(".", ""); 53 | 54 | if (!accExclusion.Contains(extension)) 55 | { 56 | fileCount++; 57 | filesInBytes += new FileInfo(filename).Length; 58 | } 59 | } 60 | } 61 | } 62 | 63 | //Convert bytes to Mega 64 | var filesInMb = (filesInBytes / 1024f) / 1024f; 65 | metrics.TotalSizeMb = filesInMb; 66 | metrics.TotalFileCount = fileCount; 67 | } 68 | catch (Exception ex) 69 | { 70 | Log.Error("Problem calculating file sizes and count for local files"); 71 | } 72 | 73 | return metrics; 74 | } 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Helpers/FolderHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace BulkUploaderUtils.Helpers 4 | { 5 | public class FolderHelpers 6 | { 7 | public static bool PathHasFolder(string pathToFileName, string folderToCheck) 8 | { 9 | bool bHasFolder = false; 10 | 11 | try 12 | { 13 | var folders = pathToFileName.Split(Path.DirectorySeparatorChar); 14 | bHasFolder = folders.Any(x => x.ToUpper() == folderToCheck.ToUpper()); 15 | } 16 | catch (Exception ex) 17 | { 18 | } 19 | 20 | return bHasFolder; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Helpers/HubHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http.Json; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace BulkUploaderUtils.Helpers 9 | { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Account.cs: -------------------------------------------------------------------------------- 1 | using EPPlus.Core.Extensions.Attributes; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace Data.Models 6 | { 7 | public class Account 8 | { 9 | [Key] 10 | [JsonPropertyName("id")] public int Id { get; set; } 11 | 12 | [ExcelTableColumn("Account ID", true)] 13 | [JsonPropertyName("accountId")] 14 | public string AccountId { get; set; } 15 | 16 | [ExcelTableColumn("Account Name", true)] 17 | [JsonPropertyName("name")] 18 | public string Name { get; set; } 19 | 20 | 21 | [ExcelTableColumn("Account Enabled", true)] 22 | [JsonPropertyName("enabled")] 23 | public bool Enabled { get; set; } 24 | 25 | [ExcelTableColumn("Account Region", true)] 26 | [JsonPropertyName("region")] 27 | public string Region { get; set; } 28 | } 29 | 30 | 31 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/BusinessUnit.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Data.Models 5 | { 6 | public class BusinessUnit 7 | { 8 | [Key] 9 | [JsonPropertyName("id")] public int Id { get; set; } 10 | 11 | [JsonPropertyName("accountId")] public string AccountId { get; set; } 12 | [JsonPropertyName("businessUnitId")] public string BusinessUnitId { get; set; } 13 | [JsonPropertyName("name")] public string Name { get; set; } 14 | [JsonPropertyName("path")] public string Path { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/ErrorMessage.cs: -------------------------------------------------------------------------------- 1 | using EPPlus.Core.Extensions.Attributes; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace BulkUploaderUtils.Models 9 | { 10 | public class ErrorMessage 11 | { 12 | public ErrorMessage(string heading, string data, string details) 13 | { 14 | Heading = heading; 15 | Data = data; 16 | Details = details; 17 | } 18 | 19 | public ErrorMessage(string heading, Exception ex) 20 | { 21 | Heading = heading; 22 | Data = ex.Message; 23 | Details = ex.StackTrace?? "No Stack"; 24 | } 25 | 26 | [ExcelTableColumn("Error", true)] public string Heading { get; } 27 | [ExcelTableColumn("Message", true)] public string Data { get; } 28 | [ExcelTableColumn("Details", true)] public string Details { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/FolderPermission.cs: -------------------------------------------------------------------------------- 1 | namespace Data.Models 2 | { 3 | public class FolderPermission 4 | { 5 | public string ProjectId { get; set; } 6 | public string FolderId { get; set; } 7 | public bool Access { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeAttributesBatchGet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Data.Models.Forge 8 | { 9 | public class ForgeAttributesBatchGet 10 | { 11 | public List results { get; set; } 12 | public List errors { get; set; } 13 | } 14 | 15 | public class ForgeAttributesBatchGetResult 16 | { 17 | public string urn { get; set; } 18 | public string itemUrn { get; set; } 19 | public string name { get; set; } 20 | public string title { get; set; } 21 | public string number { get; set; } 22 | public string createTime { get; set; } 23 | public string createUserId { get; set; } 24 | public string createUserName { get; set; } 25 | public string lastModifiedTime { get; set; } 26 | public string lastModifiedUserId { get; set; } 27 | public string lastModifiedUserName { get; set; } 28 | public string storageUrn { get; set; } 29 | public int? storageSize { get; set; } 30 | public string entityType { get; set; } 31 | public int revisionNumber { get; set; } 32 | public string processState { get; set; } 33 | public Approvalstatus approvalStatus { get; set; } 34 | public List customAttributes { get; set; } 35 | } 36 | 37 | public class Approvalstatus 38 | { 39 | public string label { get; set; } 40 | public string value { get; set; } 41 | } 42 | 43 | public class CustomAttribute 44 | { 45 | public int id { get; set; } 46 | public string type { get; set; } 47 | public string name { get; set; } 48 | public string value { get; set; } 49 | } 50 | 51 | public class Error 52 | { 53 | public string urn { get; set; } 54 | public string code { get; set; } 55 | public string title { get; set; } 56 | public string detail { get; set; } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeBim360Project.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Data.Models.Forge.Bim360Project 8 | { 9 | 10 | public class ForgeBim360Project 11 | { 12 | public string id { get; set; } 13 | public string account_id { get; set; } 14 | public string name { get; set; } 15 | public string start_date { get; set; } 16 | public string end_date { get; set; } 17 | public string project_type { get; set; } 18 | public float? value { get; set; } 19 | public string currency { get; set; } 20 | public ForgeBim360ProjectStatus status { get; set; } 21 | public string job_number { get; set; } 22 | public string address_line_1 { get; set; } 23 | public string address_line_2 { get; set; } 24 | public string city { get; set; } 25 | public string state_or_province { get; set; } 26 | public string postal_code { get; set; } 27 | public string country { get; set; } 28 | public string business_unit_id { get; set; } 29 | public string timezone { get; set; } 30 | public string language { get; set; } 31 | public string construction_type { get; set; } 32 | public string contract_type { get; set; } 33 | public object? last_sign_in { get; set; } 34 | public DateTime? created_at { get; set; } 35 | public DateTime? updated_at { get; set; } 36 | } 37 | 38 | public enum ForgeBim360ProjectStatus 39 | { 40 | Active, 41 | Pending, 42 | Inactive, 43 | Archived 44 | } 45 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeBusinessUnits.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Data.Models.Forge 8 | { 9 | 10 | public class ForgeBusinessUnitResponse 11 | { 12 | public List business_units { get; set; } 13 | } 14 | 15 | public class ForgeBusinessUnit 16 | { 17 | public string id { get; set; } 18 | public string account_id { get; set; } 19 | public string parent_id { get; set; } 20 | public string name { get; set; } 21 | public string description { get; set; } 22 | public string path { get; set; } 23 | public DateTime created_at { get; set; } 24 | public DateTime updated_at { get; set; } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeCheckPermissionResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Data.Models.Forge 2 | { 3 | 4 | public class ForgeCheckPermissionResponse 5 | { 6 | public Data data { get; set; } 7 | public Jsonapi jsonapi { get; set; } 8 | } 9 | 10 | public class Data 11 | { 12 | public string type { get; set; } 13 | public string id { get; set; } 14 | public Attributes attributes { get; set; } 15 | public Relationships relationships { get; set; } 16 | } 17 | 18 | public class Attributes 19 | { 20 | public Extension extension { get; set; } 21 | } 22 | 23 | public class Extension 24 | { 25 | public Data1 data { get; set; } 26 | public string version { get; set; } 27 | public string type { get; set; } 28 | public Schema schema { get; set; } 29 | } 30 | 31 | public class Data1 32 | { 33 | public Permission[] permissions { get; set; } 34 | public string[] requiredActions { get; set; } 35 | } 36 | 37 | public class Permission 38 | { 39 | public string type { get; set; } 40 | public string id { get; set; } 41 | public Details details { get; set; } 42 | public bool permission { get; set; } 43 | } 44 | 45 | public class Details 46 | { 47 | public bool create { get; set; } 48 | public bool download { get; set; } 49 | public bool view { get; set; } 50 | } 51 | 52 | public class Schema 53 | { 54 | public string href { get; set; } 55 | } 56 | 57 | public class Relationships 58 | { 59 | public Resources resources { get; set; } 60 | } 61 | 62 | public class Resources 63 | { 64 | public Datum[] data { get; set; } 65 | } 66 | 67 | public class Datum 68 | { 69 | public string type { get; set; } 70 | public string id { get; set; } 71 | } 72 | 73 | public class Jsonapi 74 | { 75 | public string version { get; set; } 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeFirstVersionResponse.cs: -------------------------------------------------------------------------------- 1 | namespace mass_upload_via_s3_csharp.Models.Forge; 2 | 3 | public class Attributes 4 | { 5 | public string displayName { get; set; } 6 | public DateTime createTime { get; set; } 7 | public string createUserId { get; set; } 8 | public string createUserName { get; set; } 9 | public DateTime lastModifiedTime { get; set; } 10 | public string lastModifiedUserId { get; set; } 11 | public string lastModifiedUserName { get; set; } 12 | public bool hidden { get; set; } 13 | public bool reserved { get; set; } 14 | public Extension extension { get; set; } 15 | public string name { get; set; } 16 | public int versionNumber { get; set; } 17 | } 18 | 19 | public class Data 20 | { 21 | public string type { get; set; } 22 | public string id { get; set; } 23 | public Attributes attributes { get; set; } 24 | public Relationships relationships { get; set; } 25 | public Links links { get; set; } 26 | public string conformingStatus { get; set; } 27 | } 28 | 29 | public class Extension 30 | { 31 | public string type { get; set; } 32 | public string version { get; set; } 33 | public Data data { get; set; } 34 | } 35 | 36 | public class Included 37 | { 38 | public string type { get; set; } 39 | public string id { get; set; } 40 | public Attributes attributes { get; set; } 41 | public Relationships relationships { get; set; } 42 | public Links links { get; set; } 43 | } 44 | 45 | public class Jsonapi 46 | { 47 | public string version { get; set; } 48 | } 49 | 50 | public class Links 51 | { 52 | public Self self { get; set; } 53 | public Related related { get; set; } 54 | public WebView webView { get; set; } 55 | } 56 | 57 | public class Related 58 | { 59 | public string href { get; set; } 60 | } 61 | 62 | public class Relationships 63 | { 64 | public Tip tip { get; set; } 65 | } 66 | 67 | public class ForgeFirstVersionResponse 68 | { 69 | public Jsonapi jsonapi { get; set; } 70 | public Links links { get; set; } 71 | public Data data { get; set; } 72 | public List included { get; set; } 73 | } 74 | 75 | public class Self 76 | { 77 | public string href { get; set; } 78 | } 79 | 80 | public class Tip 81 | { 82 | public Links links { get; set; } 83 | public Data data { get; set; } 84 | } 85 | 86 | public class WebView 87 | { 88 | public string href { get; set; } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeGetDataHooksResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Data.Models.Forge.GetDataHooksResponse 9 | { 10 | 11 | public class ForgeGetDataHooksResponse 12 | { 13 | public Links links { get; set; } 14 | public Datum[] data { get; set; } 15 | } 16 | 17 | public class Links 18 | { 19 | public string next { get; set; } 20 | } 21 | 22 | public class Datum 23 | { 24 | public string hookId { get; set; } 25 | public string tenant { get; set; } 26 | public string callbackUrl { get; set; } 27 | public string createdBy { get; set; } 28 | [JsonProperty("event")] 29 | public string _event { get; set; } 30 | public DateTime createdDate { get; set; } 31 | public DateTime lastUpdatedDate { get; set; } 32 | public string system { get; set; } 33 | public string creatorType { get; set; } 34 | public string status { get; set; } 35 | public Scope scope { get; set; } 36 | public bool autoReactivateHook { get; set; } 37 | public string urn { get; set; } 38 | public bool callbackWithEventPayloadOnly { get; set; } 39 | public string __self__ { get; set; } 40 | } 41 | 42 | public class Scope 43 | { 44 | public string folder { get; set; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Data.Models.Forge.Metadata 8 | { 9 | public class ForgeMetadata 10 | { 11 | public Data data { get; set; } 12 | } 13 | 14 | public class Data 15 | { 16 | public string type { get; set; } 17 | public Metadata[] metadata { get; set; } 18 | } 19 | 20 | public class Metadata 21 | { 22 | public string name { get; set; } 23 | public string role { get; set; } 24 | public string guid { get; set; } 25 | public Nullable isMasterView { get; set; } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeMetadataProperties.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Data.Models.Forge.MetadataProperties; 4 | 5 | public class ForgeMetadataProperties 6 | { 7 | public Data data { get; set; } 8 | } 9 | 10 | public class Data 11 | { 12 | public string type { get; set; } 13 | public Property[] collection { get; set; } 14 | } 15 | 16 | public class Property 17 | { 18 | public int objectid { get; set; } 19 | public string externalId { get; set; } 20 | public string name { get; set; } 21 | public Dictionary> properties { get; set; } 22 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeProjectUsers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Data.Models.Forge 8 | { 9 | public class ForgeProjectUsers 10 | { 11 | public Pagination pagination { get; set; } 12 | public Result[] results { get; set; } 13 | } 14 | 15 | public class Pagination 16 | { 17 | public int limit { get; set; } 18 | public int offset { get; set; } 19 | public int totalResults { get; set; } 20 | public string nextUrl { get; set; } 21 | public string previousUrl { get; set; } 22 | } 23 | 24 | public class Result 25 | { 26 | public string id { get; set; } 27 | public string email { get; set; } 28 | public string name { get; set; } 29 | // public string firstName { get; set; } 30 | // public string lastName { get; set; } 31 | // public string autodeskId { get; set; } 32 | // public string anaylticsId { get; set; } 33 | // public string addressLine1 { get; set; } 34 | // public string addressLine2 { get; set; } 35 | // public string city { get; set; } 36 | // public string stateOrProvince { get; set; } 37 | // public int postalCode { get; set; } 38 | // public string country { get; set; } 39 | // public string imageUrl { get; set; } 40 | // public Phone phone { get; set; } 41 | // public string jobTitle { get; set; } 42 | // public string industry { get; set; } 43 | // public string aboutMe { get; set; } 44 | // public Accesslevels accessLevels { get; set; } 45 | // public string companyId { get; set; } 46 | // public string[] roleIds { get; set; } 47 | // public Service[] services { get; set; } 48 | } 49 | 50 | public class Phone 51 | { 52 | public string number { get; set; } 53 | public string phoneType { get; set; } 54 | public string extension { get; set; } 55 | } 56 | 57 | public class Accesslevels 58 | { 59 | public bool accountAdmin { get; set; } 60 | public bool projectAdmin { get; set; } 61 | public bool executive { get; set; } 62 | } 63 | 64 | public class Service 65 | { 66 | public string serviceName { get; set; } 67 | public string access { get; set; } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeS3UploadCompletion.cs: -------------------------------------------------------------------------------- 1 | namespace mass_upload_via_s3_csharp.Models.Forge; 2 | 3 | public class ForgeS3UploadCompletion 4 | { 5 | public string bucketKey { get; set; } 6 | public string objectId { get; set; } 7 | public string objectKey { get; set; } 8 | public int size { get; set; } 9 | public string contentType { get; set; } 10 | public string location { get; set; } 11 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeSignedS3Upload.cs: -------------------------------------------------------------------------------- 1 | namespace mass_upload_via_s3_csharp.Models.Forge.ForgeSignedS3Upload; 2 | 3 | public class ForgeSignedS3Upload 4 | { 5 | public string uploadKey { get; set; } 6 | public DateTime uploadExpiration { get; set; } 7 | public DateTime urlExpiration { get; set; } 8 | public List urls { get; set; } 9 | 10 | } 11 | 12 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeSignedS3Url.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Data.Models.Forge 8 | { 9 | public class ForgeSignedS3Url 10 | { 11 | public string status { get; set; } 12 | public string url { get; set; } 13 | public Params _params { get; set; } 14 | public int size { get; set; } 15 | public string sha1 { get; set; } 16 | } 17 | 18 | public class Params 19 | { 20 | public string contenttype { get; set; } 21 | public string contentdisposition { get; set; } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeStorageCreation.cs: -------------------------------------------------------------------------------- 1 | namespace mass_upload_via_s3_csharp.Models.Forge.ForgeStorageCreation; 2 | 3 | // Root myDeserializedClass = JsonConvert.DeserializeObject(myJsonResponse); 4 | public class Data 5 | { 6 | public string type { get; set; } 7 | public string id { get; set; } 8 | public Relationships relationships { get; set; } 9 | } 10 | 11 | public class Jsonapi 12 | { 13 | public string version { get; set; } 14 | } 15 | 16 | public class Links 17 | { 18 | public Self self { get; set; } 19 | public Related related { get; set; } 20 | } 21 | 22 | public class Related 23 | { 24 | public string href { get; set; } 25 | } 26 | 27 | public class Relationships 28 | { 29 | public Target target { get; set; } 30 | } 31 | 32 | public class ForgeStorageCreation 33 | { 34 | public Jsonapi jsonapi { get; set; } 35 | public Links links { get; set; } 36 | public Data data { get; set; } 37 | } 38 | 39 | public class Self 40 | { 41 | public string href { get; set; } 42 | } 43 | 44 | public class Target 45 | { 46 | public Links links { get; set; } 47 | public Data data { get; set; } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeSupportedFormats.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Data.Models.Forge; 4 | 5 | public class ForgeSupportedFormats 6 | { 7 | public Formats formats { get; set; } 8 | } 9 | 10 | public class Formats 11 | { 12 | public List dwg { get; set; } 13 | public List fbx { get; set; } 14 | public List ifc { get; set; } 15 | public List iges { get; set; } 16 | public List obj { get; set; } 17 | public List step { get; set; } 18 | public List stl { get; set; } 19 | public List svf { get; set; } 20 | public List svf2 { get; set; } 21 | public List thumbnail { get; set; } 22 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeUserInformation.cs: -------------------------------------------------------------------------------- 1 | namespace Data.Models.Forge 2 | { 3 | 4 | public class ForgeUserInformation 5 | { 6 | public string userId { get; set; } 7 | public string userName { get; set; } 8 | public string emailId { get; set; } 9 | public string firstName { get; set; } 10 | public string lastName { get; set; } 11 | public bool emailVerified { get; set; } 12 | public bool _2FaEnabled { get; set; } 13 | public string countryCode { get; set; } 14 | public string language { get; set; } 15 | public bool optin { get; set; } 16 | public string lastModified { get; set; } 17 | public Profileimages profileImages { get; set; } 18 | public Ldapinfo ldapInfo { get; set; } 19 | public object[] socialUserInfoList { get; set; } 20 | public string createdDate { get; set; } 21 | public Userprofessionalinfo userProfessionalInfo { get; set; } 22 | public string lastLoginDate { get; set; } 23 | public string eidmGuid { get; set; } 24 | } 25 | 26 | public class Profileimages 27 | { 28 | public string sizeX20 { get; set; } 29 | public string sizeX40 { get; set; } 30 | public string sizeX50 { get; set; } 31 | public string sizeX58 { get; set; } 32 | public string sizeX80 { get; set; } 33 | public string sizeX120 { get; set; } 34 | public string sizeX160 { get; set; } 35 | public string sizeX176 { get; set; } 36 | public string sizeX240 { get; set; } 37 | public string sizeX360 { get; set; } 38 | } 39 | 40 | public class Ldapinfo 41 | { 42 | public bool ldapEnabled { get; set; } 43 | public string domainName { get; set; } 44 | } 45 | 46 | public class Userprofessionalinfo 47 | { 48 | public string industry { get; set; } 49 | public string industryCode { get; set; } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Forge/ForgeWebhookResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Data.Models.Forge.WebhookResponse 8 | { 9 | 10 | public class ForgeWebhookResponse 11 | { 12 | public Hook[] hooks { get; set; } 13 | } 14 | 15 | public class Hook 16 | { 17 | public string hookId { get; set; } 18 | public string tenant { get; set; } 19 | public string callbackUrl { get; set; } 20 | public string createdBy { get; set; } 21 | public string _event { get; set; } 22 | public DateTime createdDate { get; set; } 23 | public string system { get; set; } 24 | public string creatorType { get; set; } 25 | public Scope scope { get; set; } 26 | public string status { get; set; } 27 | public bool autoReactivateHook { get; set; } 28 | public string urn { get; set; } 29 | public string __self__ { get; set; } 30 | } 31 | 32 | public class Scope 33 | { 34 | public string folder { get; set; } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/ForgeBatchS3Download.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Data.Models.Forge 5 | { 6 | public class ForgeBatchS3Download 7 | { 8 | public Dictionary results { get; set; } 9 | } 10 | 11 | public class ForgeBatchS3DownloadItem 12 | { 13 | public string status { get; set; } 14 | public string reason { get; set; } 15 | public string url { get; set; } 16 | // public Nullable> urls { get; set; } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/Project.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | using Data.Managers; 8 | using Data.Models.Forge.Bim360Project; 9 | using EPPlus.Core.Extensions.Attributes; 10 | using Newtonsoft.Json; 11 | using Newtonsoft.Json.Converters; 12 | 13 | namespace Data.Models 14 | { 15 | public class Project 16 | { 17 | [Key] 18 | [JsonPropertyName("id")] public int Id { get; set; } 19 | 20 | [ExcelTableColumn("BU ID", true)] 21 | [JsonPropertyName("businessUnitId")] 22 | public string BusinessUnitId { get; set; } 23 | 24 | [ExcelTableColumn("Account ID", true)] 25 | [JsonPropertyName("accountId")] public string 26 | AccountId { get; set; } 27 | 28 | [ExcelTableColumn("Project ID", true)] 29 | [JsonPropertyName("projectId")] 30 | public string ProjectId { get; set; } 31 | 32 | [ExcelTableColumn("Project Name", true)] 33 | [JsonPropertyName("name")] 34 | public string Name { get; set; } 35 | 36 | [ExcelTableColumn("Project Status", true)] 37 | [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))] 38 | [JsonPropertyName("projectType")] 39 | public ProjectType ProjectType { get; set; } 40 | [JsonPropertyName("status")] public bool Status { get; set; } 41 | 42 | [ExcelTableColumn("Project Wieght", true)] 43 | [JsonPropertyName("weight")] 44 | public int Weight { get; set; } 45 | } 46 | 47 | public enum ProjectType 48 | { 49 | ACC, 50 | BIM360 51 | } 52 | 53 | 54 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/SimpleFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Data.Models.Forge; 4 | using EPPlus.Core.Extensions.Attributes; 5 | 6 | namespace Data.Models 7 | { 8 | public class SimpleFile 9 | { 10 | [ExcelTableColumn("Item ID", true)] 11 | public string ItemId { get; set; } 12 | [ExcelTableColumn("Version ID", true)] 13 | public string VersionId { get; set; } 14 | [ExcelTableColumn("Derivative ID", true)] 15 | public string DerivativeId { get; set; } 16 | [ExcelTableColumn("Storage ID", true)] 17 | public string ObjectId { get; set; } 18 | [ExcelTableColumn("Name", true)] 19 | public string Name { get; set; } 20 | [ExcelTableColumn("File Type", true)] 21 | public string FileType { get; set; } 22 | [ExcelTableColumn("Item URL", true)] 23 | public string Url { get; set; } 24 | [ExcelTableColumn("Last Modified", true)] 25 | public DateTime LastModified { get; set; } 26 | [ExcelTableColumn("Parent", true)] 27 | public string? ParentPath { get; set; } 28 | [ExcelTableColumn("Path", true)] 29 | public string? Path { get; set; } 30 | [ExcelTableColumn("Size", true)] 31 | public long Size { get; set; } 32 | [ExcelTableColumn("Status", true)] 33 | public string Status { get; set; } 34 | 35 | 36 | public List CustomAttributes { get; set; } = new List(); 37 | } 38 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/SimpleFolder.cs: -------------------------------------------------------------------------------- 1 | using EPPlus.Core.Extensions.Attributes; 2 | 3 | namespace Data.Models 4 | { 5 | public class SimpleFolder 6 | { 7 | [ExcelTableColumn("Folder ID", true)] 8 | public string FolderId { get; set; } 9 | [ExcelTableColumn("Folder Name", true)] 10 | public string Name { get; set; } 11 | [ExcelTableColumn("Folder URL", true)] 12 | public string Url { get; set; } 13 | [ExcelTableColumn("Parent", true)] 14 | public string? ParentPath { get; set; } 15 | [ExcelTableColumn("Path", true)] 16 | public string? Path { get; set; } 17 | [ExcelTableColumn("Is Root", true)] 18 | public bool IsRoot { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/SimpleHook.cs: -------------------------------------------------------------------------------- 1 | namespace Data.Models 2 | { 3 | public class SimpleHook 4 | { 5 | public string HookId { get; set; } 6 | public string Event { get; set; } 7 | public string System { get; set; } 8 | public string Callback { get; set; } 9 | public string FolderId { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Models/ThreeLeggedToken.cs: -------------------------------------------------------------------------------- 1 | namespace Data.Models 2 | { 3 | public class ThreeLeggedToken 4 | { 5 | public string AccessToken { get; set; } 6 | public string RefreshToken { get; set; } 7 | public string TokenType { get; set; } 8 | public int ExpiresIn { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /BulkUploaderUtils/Utils/MappingProfiles.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Data.Models; 3 | 4 | namespace BulkUploaderUtils.Utils 5 | { 6 | public static class ClassMappings 7 | { 8 | public static readonly IMapper Mapper; 9 | 10 | 11 | /// 12 | /// Configures a static instance of a mapper 13 | /// 14 | static ClassMappings() 15 | { 16 | MapperConfiguration config = new MapperConfiguration(cfg => cfg.AddProfile()); 17 | Mapper = config.CreateMapper(); 18 | } 19 | } 20 | 21 | /// 22 | /// Defines set of Automapper profiles 23 | /// 24 | public class MigrationProfiles : Profile 25 | { 26 | public MigrationProfiles() 27 | { 28 | // CreateMap().ReverseMap(); 29 | 30 | // Use CreateMap... Etc.. here (Profile methods are the same as configuration methods) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Documentation/20230817-Upload-Download-Coordination.drawio: -------------------------------------------------------------------------------- 1 | 7Vxbj5s4FP418zgVYGzI49xHVautNN1L+7JygkPoEBw5TjPpr187kAu2k0kVsEmyqdQJBxPgO+cz52auwN347YnhyegzTUh+FXjJ2xW4vwqCII5i8UdKFqUkCkApSFmWlCJ/I3jJfpFK6FXSWZaQaW0gpzTn2aQuHNCiIANek2HG6Lw+bEjz+lknOCWa4GWAc136d5bwUSWNPG+z45lk6Wh1an+1Z4xXoyvBdIQTOt8SgYcrcMco5eW38dsdySV6K2DK4x537F1fGSMFP+QA8P3fnHy/vkXR0/Nfv2bTP54RvkaVfn7ifFbdcnW1fLHCgNFZkRD5K94VuJ2PMk5eJngg986F1oVsxMe52PLF12GW53c0p2x5LEggiZNQyKec0VeytScO+gAhsae6AMI4edt5a/4aMGFqhI4JZwsxZGVnsMK4sjJ/tT3f6AysZKMtdQUrbeHKTtL1b2+QFF8qMH8HWNQusEMo/0k5LfiWvPyYAEfLTzOAX/tIQVwHPAgNgIdBW4CvznaegMO4jncIDgQ8bgtvEJ0z3kixbxgeiHeAWgMcnjPgfqAYOHIOuB9p+JJEeAfVJmV8RFNa4PxhI72ta2Az5hOlkwr3H4TzReXq4Bmnda0IvNjin+r45cY3ufEBrjbv37Z33i+qrfJa5QXu14C4HzpjA7LvxstxHLOU8H3jQrNKGckxz37WL8SknuWhN4zhxdaACc0KPt365S9SsPWwV6kZQUXV5S9uFL++tCNsQePeR9r/iqevN2nKSIq54ESjfhMm8XBgJN0gJv1hQ34TUEgX66RbE3ObdK1xDrml3IZl32oka51ywaGUO5JxRynH8XzYeeVAl8oJTDPULeaDUbNewnAYDIwTU4L6CDbkDQCvPjEBgzdgd2LqXajtgwNt34kroHnpQEmBKONhb+/4llwH73/T2Ws68SmYTgQcmA7Q5vTbWf765ySnODnBWR11blYP9wD8mAlkT8+nh6qhGnKhdkHW8xbnEDuBqGuxk57KP3ljRlHXjNnXk26X8TT3D80KBS4e59BXHi1qMUwZD9De8S15gsCF7biwAXAKNqC6gHZsQM8kfs5SJmChhXgsNjpHD4cE7fDqol7f85qZo0O1+Bo5n6N1t+6Fk8lUA/cdOOvYNwBVoFTxoMEBjgxIgdaQit0+zdoqcbyfR+01MUMdUJtQNFdOsdVRG+UdWwNBvo0aSO9EaLWOM13Rav1AvTRaBY04fweYuyVaQQu0CoJToZWhB8IureCl0grZoRW0RKueDVrpDXgdpZUhcWSXVmfa5/I+rRqpPOi5hV7vQ2/rE9fUve4ka5hkoae6njZIpuccu0GydZ9pVyKtwHEB2RnJgGdW1JEk083dDq2sRForK+w8rZxHWuBSIy3QTqSlm7slWtmItEBXIy2NVq4jLeCkUtEFWjXSx3SAuVuilY1IC3Q13a7RynWkBfSY9BMt0mtO2FjeWY4LDTRx+7yOU72aU9CCKKWfSoTzLC3E5kDARYT8VoKZDXB+U+0YZ0myZK5JMY1r45A1Y+vVk3bUsTN68cRdif/7syyX7BIco/KicJEOM6Y3UpyNjiK1QRJB1zrSiw5fGJlT9iqEH2lfn2V+q2LaAGZqV1toeHhb7mrTa8yXbtfq6jLo2q6hrqOb6XQ2JlJLfISlevDyZwS6ZKO614LOC3nvLJNtc95SgQNGhHdSpOLrj11N/2ejS/UxAkyNd8CqLvW4Qnbe3QtNnWinrrr+InDe3Aj1Xuhm1rc04tmoySjnaIUaKJcRv8Fj12CZ46prpWbjt5QW0c4TWojfoN6g3Y347VoNGAwvPbAav0HHy1Pd0SqyQ6uWamPaeWIbtNJfkNFRWhkaNu3S6lK7EGE7XYi6uduhVWDlaaUnBJ5IQRjmMjx6LIOljjHMlOoyMUytYTb3WgXvQhmGjn1hwg7LBzvio6YZppwH2Kg+o53pI9e0Uhd4uK4+o+BSadXIAp8DzN0SrWxUn5GewegorVxXn9GlZi9QS9kLzdwt0cpG9Rl1Nnuh0sp19RnpAekDlulTTwImj16WGSRqWZHK9eJblSFZWNAgPZ+aQljXFTStYbRaH0KGxVYjMasJ0deyW+DLWXcLBIpGTK/0barKIzY3r2Eu56XN26zBw38= -------------------------------------------------------------------------------- /Documentation/BalsamiqWireframes.bmpr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/BalsamiqWireframes.bmpr -------------------------------------------------------------------------------- /Documentation/Readme_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/Readme_img.png -------------------------------------------------------------------------------- /Documentation/Thumnail1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/Thumnail1.gif -------------------------------------------------------------------------------- /Documentation/Thumnail_bulk.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/Thumnail_bulk.gif -------------------------------------------------------------------------------- /Documentation/User_guide_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/User_guide_0.png -------------------------------------------------------------------------------- /Documentation/User_guide_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/User_guide_1.png -------------------------------------------------------------------------------- /Documentation/User_guide_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/User_guide_2.png -------------------------------------------------------------------------------- /Documentation/User_guide_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/User_guide_3.png -------------------------------------------------------------------------------- /Documentation/User_guide_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/User_guide_4.png -------------------------------------------------------------------------------- /Documentation/User_guide_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/User_guide_5.png -------------------------------------------------------------------------------- /Documentation/User_guide_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/User_guide_6.png -------------------------------------------------------------------------------- /Documentation/Wireframes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-hubs-bulk-files-manager/6ea61fa3b64182e4c564c19e8630350e2e273e38/Documentation/Wireframes.pdf -------------------------------------------------------------------------------- /Documentation/developer-guide.md: -------------------------------------------------------------------------------- 1 | # Developer Guide 2 | 3 | This is a help document with notes when you want to debug or extend the ability of this application. 4 | 5 | ## Layout 6 | 7 | This repository comprises the following top level folders. 8 | 9 | - [Ac.Net.Authentication](../Ac.Net.Authentication): help project that manages the workflow of user context authentication (3-legged token) of APS. 10 | - [ApsSettings.Data](../ApsSettings.Data):data models 11 | - [Bulk-Uploader-FrontEnd](../Bulk-Uploader-FrontEnd): main project, including user interfaces 12 | - [BulkUploaderUtils](../BulkUploaderUtils): help functions for upload/download 13 | - [Documentation](../Documentation): documentations and screenshots 14 | - [MigrationPlugin](../MigrationPlugin): This is project will have batch jobs controller to initiate the background jobs 15 | - [PluginBase](../PluginBase): This Plugins have models based out of Hangfire Btach jobs. 16 | - [TestPlugin](../TestPlugin): This is a Test project will help developer to test the scenarios and user/developers can add their own test cases if needed. 17 | 18 | 19 | ## Communication between UI and Backend 20 | 21 | There are two ways to communicate between the host and the UI: 22 | * REST calls 23 | * Standard ASP.net controllers may be used to send messages from the UI to the backend. 24 | * PostMessage 25 | * The UI is able to send messages to the backend using `window.chrome.webview.postMessage(YOUR_MESSAGE_HERE)`. 26 | It is then processed by the `WebMessageReceived()` method in `Bulk-Uploader-FrontEnd/Views/MainForm.cs`. 27 | * The backend is able to respond using `this.webView21.CoreWebView2.PostWebMessageAsString(YOUR_MESSAGE_HERE)`. It 28 | is then processed by the UI using `window.chrome.webview.addEventListener('message', event=>{ alert(event.data) })` 29 | in `Bulk-Uploader-FrontEnd/ClientApp/src/App.tsx`. 30 | * This has been simplified on the front end with the `useMessageListener(messagename)` hook. 31 | 32 | ## React Notes 33 | 34 | When using `` to go between the SPA and other pages (such as the hangfire dashboard), you must use the `reloadDocument` parameter to force React Router to treat it as a normal `` element. 35 | 36 | ## Database 37 | 38 | This application uses Entity Framework. If you make any changes to the data structures, you'll need to run the following command from the `Bulk-Uploader-FrontEnd` folder: 39 | 40 | `dotnet ef migrations add NameOfYourMigrationHere --project "..\ApsSettings.Data\ApsSettings.Data.csproj"` 41 | 42 | ## Additional Notes 43 | - In debugging mode, the `flower icon` on the bottom right corner will show up. It is the console that tracks the logs of each HTTP traffic. 44 | 45 | ## Building 46 | To build the application for a windows machine, use dotnet publish -r win-x64 while in the root solution folder. 47 | 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Autodesk Platform Services 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Autodesk Inc. http://www.autodesk.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MigrationPlugin/Batches/ApsDownloadBatch.cs: -------------------------------------------------------------------------------- 1 | using ApsSettings.Data; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using MigrationPlugin.Controllers; 4 | using MigrationPlugin.Plugin; 5 | using PluginBase.Models; 6 | 7 | namespace MigrationPlugin.Batches 8 | { 9 | internal class ApsDownloadBatch : IBatchRegistry 10 | { 11 | public string Name => "APS Download"; 12 | 13 | public HfBatchOperation BatchExecute => throw new NotImplementedException(); 14 | 15 | public HfBatchOperation BuildStack => throw new NotImplementedException(); 16 | 17 | public HfBatchOperation GetTemplate => throw new NotImplementedException(); 18 | 19 | public HfBatchLoad LoadBatch => throw new NotImplementedException(); 20 | 21 | public BatchDetails BatchDetails => new BatchDetails 22 | { 23 | Title = "Download", 24 | Key = "download_v1", 25 | Description = "Basic Download of Files from the Autodesk Cloud", 26 | BatchLoadUrl = BatchDownloadController.PATH_LOAD, 27 | BatchTemplateUrl = BatchDownloadController.PATH_TEMPLATE 28 | }; 29 | 30 | public HfBatchOperation BatchEnqueue => EnqueueStart; 31 | 32 | private BatchProcessResults EnqueueStart(BatchJob batchJob) 33 | { 34 | try 35 | { 36 | var context = MigrationPluginObj.Instance.ServiceProvider.GetService()!; 37 | } 38 | catch (Exception ex) 39 | { 40 | throw; 41 | } 42 | 43 | return null; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /MigrationPlugin/Batches/ApsMigrateBatch.cs: -------------------------------------------------------------------------------- 1 | using MigrationPlugin.Controllers; 2 | using PluginBase.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MigrationPlugin.Batches 10 | { 11 | public class ApsMigrateBatch : IBatchRegistry 12 | { 13 | public string Name => "APS Simple Migrate"; 14 | 15 | 16 | public HfBatchOperation BatchExecute => throw new NotImplementedException(); 17 | 18 | public HfBatchOperation BuildStack => throw new NotImplementedException(); 19 | 20 | public HfBatchOperation GetTemplate => throw new NotImplementedException(); 21 | 22 | public HfBatchLoad LoadBatch => throw new NotImplementedException(); 23 | 24 | public BatchDetails BatchDetails => new BatchDetails 25 | { 26 | Title = "Migration", 27 | Key = "migrate_v1", 28 | Description = "Basic Migration of Files to the Autodesk Cloud", 29 | BatchLoadUrl = BatchBasicMigrationController.PATH_LOAD, 30 | BatchTemplateUrl = BatchBasicMigrationController.PATH_TEMPLATE 31 | }; 32 | 33 | public HfBatchOperation BatchEnqueue => throw new NotImplementedException(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /MigrationPlugin/Batches/ApsUploadBatch.cs: -------------------------------------------------------------------------------- 1 | using MigrationPlugin.Controllers; 2 | using PluginBase.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace MigrationPlugin.Batches 10 | { 11 | internal class ApsUploadBatch : IBatchRegistry 12 | { 13 | public string Name => "APS Upload"; 14 | 15 | public HfBatchOperation BatchExecute => throw new NotImplementedException(); 16 | 17 | public HfBatchOperation BuildStack => throw new NotImplementedException(); 18 | 19 | public HfBatchOperation GetTemplate => throw new NotImplementedException(); 20 | 21 | public HfBatchLoad LoadBatch => throw new NotImplementedException(); 22 | 23 | public BatchDetails BatchDetails => new BatchDetails 24 | { 25 | Title = "Upload", 26 | Key="upload_v1", 27 | Description = "Basic Upload of Files to the Autodesk Cloud", 28 | BatchLoadUrl = BatchUploadController.PATH_LOAD, 29 | BatchTemplateUrl = BatchUploadController.PATH_TEMPLATE 30 | }; 31 | 32 | public HfBatchOperation BatchEnqueue => throw new NotImplementedException(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /MigrationPlugin/Controllers/BatchBasicMigrationController.cs: -------------------------------------------------------------------------------- 1 | using Ac.Net.Authentication; 2 | using Ac.Net.Authentication.Models; 3 | 4 | using Flurl.Http; 5 | using Ganss.Excel; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.EntityFrameworkCore; 9 | using MigrationPlugin.Excel; 10 | using MigrationPlugin.Models; 11 | using NPOI.SS.Formula.Functions; 12 | using System.IO; 13 | using System.Text; 14 | 15 | namespace MigrationPlugin.Controllers 16 | { 17 | public class BatchBasicMigrationController : Controller 18 | { 19 | const string operation = "migrate"; 20 | public const string PATH_TEMPLATE = "api/batch/" + operation + "/template"; 21 | public const string PATH_LOAD = "api/batch/" + operation + "/load"; 22 | [HttpGet] 23 | [Route(PATH_TEMPLATE)] 24 | public async Task GetTemplate() 25 | { 26 | try 27 | { 28 | var fileName = $"Batch.Download.Template.{DateTime.Now.ToString("yy.MM.dd.mm.ss")}.xlsx"; 29 | var path = Path.Combine(Path.GetTempPath(), fileName); 30 | var bytes = ExcelUtils.MockBatchDownloadXlsx(); 31 | return File(bytes, "application/vnd.ms-excel"); 32 | } 33 | catch (Exception ex) 34 | { 35 | return BadRequest(); 36 | } 37 | } 38 | 39 | [HttpPost] 40 | [Route(PATH_LOAD)] 41 | public async Task UploadData([FromForm] IFormFile FileData) 42 | { 43 | 44 | try 45 | { 46 | string name = FileData.FileName; 47 | string extension = Path.GetExtension(FileData.FileName); 48 | 49 | using (var memoryStream = new MemoryStream()) 50 | { 51 | FileData.CopyTo(memoryStream); 52 | var items = new ExcelMapper(memoryStream).Fetch(); 53 | // var items = ExcelUtils.LoadBatchDownloadXlsx(memoryStream); 54 | 55 | //await BatchFlows.ProcessDownloadBatch(ThreeLeggedMananger.Instance, DataContext context, items); 56 | } 57 | 58 | return Ok(); 59 | 60 | } 61 | catch (Exception ex) 62 | { 63 | return BadRequest(); 64 | } 65 | } 66 | 67 | 68 | 69 | } 70 | } 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /MigrationPlugin/Controllers/BatchDownloadController.cs: -------------------------------------------------------------------------------- 1 | using Ac.Net.Authentication; 2 | using Ac.Net.Authentication.Models; 3 | using ApsSettings.Data; 4 | using Ganss.Excel; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using MigrationPlugin.Excel; 9 | using MigrationPlugin.Models; 10 | using MigrationPlugin.Plugin; 11 | using Newtonsoft.Json; 12 | using PluginBase.Models; 13 | 14 | namespace MigrationPlugin.Controllers 15 | { 16 | public class BatchDownloadController : Controller 17 | { 18 | private const string operation = "download"; 19 | public const string PATH_TEMPLATE = "api/batch/" + operation + "/template"; 20 | public const string PATH_LOAD = "api/batch/" + operation + "/load"; 21 | 22 | [HttpGet] 23 | [Route(PATH_TEMPLATE)] 24 | public async Task GetTemplate() 25 | { 26 | try 27 | { 28 | var fileName = $"Batch.Download.Template.{DateTime.Now.ToString("yy.MM.dd.mm.ss")}.xlsx"; 29 | var path = Path.Combine(Path.GetTempPath(), fileName); 30 | var bytes = ExcelUtils.MockBatchDownloadXlsx(); 31 | return File(bytes, "application/vnd.ms-excel"); 32 | } 33 | catch (Exception ex) 34 | { 35 | return BadRequest(); 36 | } 37 | } 38 | 39 | [HttpPost] 40 | [Route(PATH_LOAD)] 41 | public async Task UploadData([FromForm] IFormFile FileData) 42 | { 43 | try 44 | { 45 | string name = FileData.FileName; 46 | string extension = Path.GetExtension(FileData.FileName); 47 | 48 | using (var memoryStream = new MemoryStream()) 49 | { 50 | FileData.CopyTo(memoryStream); 51 | var items = new ExcelMapper(memoryStream).Fetch().ToList(); 52 | var context = MigrationPluginObj.Instance.ServiceProvider.GetService()!; 53 | foreach (var item in items) 54 | { 55 | var job = new BatchJob() 56 | { 57 | Name = item.Name, 58 | Type = "download", 59 | Data = JsonConvert.SerializeObject(item), 60 | }; 61 | context.Batches.Add(job); 62 | } 63 | context.SaveChanges(); 64 | } 65 | 66 | return Ok(); 67 | } 68 | catch (Exception ex) 69 | { 70 | return BadRequest(); 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /MigrationPlugin/Controllers/BatchUploadController.cs: -------------------------------------------------------------------------------- 1 | using Ac.Net.Authentication; 2 | using Ac.Net.Authentication.Models; 3 | 4 | using Flurl.Http; 5 | using Ganss.Excel; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.EntityFrameworkCore; 9 | using MigrationPlugin.Excel; 10 | using MigrationPlugin.Models; 11 | using NPOI.SS.Formula.Functions; 12 | using System.IO; 13 | using System.Text; 14 | 15 | namespace MigrationPlugin.Controllers 16 | { 17 | public class BatchUploadController : Controller 18 | { 19 | const string operation = "upload"; 20 | public const string PATH_TEMPLATE = "api/batch/" + operation + "/template"; 21 | public const string PATH_LOAD = "api/batch/" + operation + "/load"; 22 | [HttpGet] 23 | [Route(PATH_TEMPLATE)] 24 | public async Task GetTemplate() 25 | { 26 | try 27 | { 28 | var fileName = $"Batch.Download.Template.{DateTime.Now.ToString("yy.MM.dd.mm.ss")}.xlsx"; 29 | var path = Path.Combine(Path.GetTempPath(), fileName); 30 | var bytes = ExcelUtils.MockBatchDownloadXlsx(); 31 | return File(bytes, "application/vnd.ms-excel"); 32 | 33 | } 34 | catch (Exception ex) 35 | { 36 | return BadRequest(); 37 | } 38 | } 39 | 40 | [HttpPost] 41 | [Route(PATH_LOAD)] 42 | public async Task UploadData([FromForm] IFormFile FileData) 43 | { 44 | 45 | try 46 | { 47 | string name = FileData.FileName; 48 | string extension = Path.GetExtension(FileData.FileName); 49 | 50 | using (var memoryStream = new MemoryStream()) 51 | { 52 | FileData.CopyTo(memoryStream); 53 | var items = new ExcelMapper(memoryStream).Fetch(); 54 | // var items = ExcelUtils.LoadBatchDownloadXlsx(memoryStream); 55 | 56 | //await BatchFlows.ProcessDownloadBatch(ThreeLeggedMananger.Instance, DataContext context, items); 57 | } 58 | 59 | return Ok(); 60 | 61 | } 62 | catch (Exception ex) 63 | { 64 | return BadRequest(); 65 | } 66 | } 67 | 68 | 69 | 70 | } 71 | } 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /MigrationPlugin/MigrationPlugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /MigrationPlugin/Models/IBatchRegistry.cs: -------------------------------------------------------------------------------- 1 | namespace MigrationPlugin.Models 2 | { 3 | 4 | } -------------------------------------------------------------------------------- /MigrationPlugin/Plugin/MigrationPluginObj.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using NPOI.SS.Formula.Functions; 5 | using PluginBase; 6 | using PluginBase.Models; 7 | 8 | namespace MigrationPlugin.Plugin 9 | { 10 | public class MigrationPluginObj : IJobExecutionPlugin 11 | { 12 | public MigrationPluginObj() 13 | { 14 | if (_instance != null) 15 | throw new NotSupportedException($"Only one instance of the MigrationPlugin plugin is supported"); 16 | _instance = this; 17 | } 18 | public static MigrationPluginObj Instance => _instance; 19 | private static MigrationPluginObj _instance; 20 | 21 | public IServiceProvider ServiceProvider { get; private set; } 22 | 23 | 24 | public void Configure(IApplicationBuilder app, IHostEnvironment env, IServiceProvider serviceProvider) 25 | { 26 | ServiceProvider = serviceProvider; 27 | ISettingsProvider sp = ServiceProvider.GetService()!; 28 | sp.RegisterGlobalSetting("OutputFolder", null); 29 | 30 | } 31 | 32 | public void ConfigureServices(IServiceCollection services) 33 | { 34 | } 35 | 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /PackageApplication.ps1: -------------------------------------------------------------------------------- 1 | 2 | $now = Get-Date -Format "yyyyMMdd" 3 | $zipFileName = "$now - Bulk File Manager - APPLICATION BUNDLE.zip" 4 | 5 | Compress-Archive ` 6 | -Path ` 7 | "./BUILD/ClientApp", ` 8 | "./BUILD/Bulk-File-Manager.exe", ` 9 | "./BUILD/WebView2Loader.dll", ` 10 | "./BUILD/e_sqlite3.dll", ` 11 | "./BUILD/v8-base-ia32.dll", ` 12 | "./BUILD/v8-base-x64.dll", ` 13 | "./BUILD/v8-ia32.dll", ` 14 | "./BUILD/v8-x64.dll", ` 15 | "./BUILD/v8-zlib-ia32.dll", ` 16 | "./BUILD/v8-zlib-x64.dll" ` 17 | -DestinationPath "$zipFileName" ` 18 | -Force 19 | -------------------------------------------------------------------------------- /PluginBase/ISettingsProvider.cs: -------------------------------------------------------------------------------- 1 | namespace PluginBase 2 | { 3 | public interface ISimpleSetting 4 | { 5 | int SettingId { get; set; } 6 | string Key { get; set; } 7 | string Value { get; set; } 8 | } 9 | 10 | public enum GlobalKeyResult 11 | { 12 | Added, 13 | Exists 14 | } 15 | 16 | public interface ISettingsProvider 17 | { 18 | ISimpleSetting GetSetting(string key, bool force = true); 19 | 20 | //ISimpleSetting[] GetSettings(string[] keys, bool force); 21 | 22 | ISimpleSetting SetSetting(string key, string value, bool force = true); 23 | 24 | //ISimpleSetting[] SetSettings(ISimpleSetting[] settings, bool force); 25 | 26 | public GlobalKeyResult RegisterGlobalSetting(string key, string? defaultValue); 27 | } 28 | } -------------------------------------------------------------------------------- /PluginBase/Models/BatchDetails.cs: -------------------------------------------------------------------------------- 1 | namespace PluginBase.Models 2 | { 3 | public class BatchDetails 4 | { 5 | public string Title { get; set; } 6 | public string Key { get; set; } 7 | public string Description { get; set; } 8 | public string BatchTemplateUrl { get; set; } 9 | public string BatchLoadUrl { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /PluginBase/Models/BatchJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PluginBase.Models 5 | { 6 | public class BatchJob 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Name { get; set; } 11 | 12 | public string Type { get; set; } 13 | 14 | public List Jobs { get; set; } = new List(); 15 | 16 | public string? Errors { get; set; } 17 | 18 | public int JobCount { get; } 19 | 20 | public string? Data { get; set; } 21 | 22 | public DateTime? Created { get; set; } = DateTime.Now; 23 | 24 | public DateTime? Queued { get; set; } 25 | 26 | public DateTime? Started { get; set; } 27 | 28 | public DateTime? Completed { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /PluginBase/Models/BatchProcessPayload.cs: -------------------------------------------------------------------------------- 1 | namespace PluginBase.Models 2 | { 3 | public class BatchProcessPayload 4 | { 5 | public int DbId { get; set; } 6 | 7 | public string BatchId { get; set; } 8 | 9 | public string Action { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /PluginBase/Models/BatchProcessResults.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PluginBase.Models 4 | { 5 | public class BatchProcessResults 6 | { 7 | public int DbId { get; set; } 8 | 9 | public string BatchId { get; set; } 10 | 11 | public string Status { get; set; } 12 | 13 | public List Errors { get; set; } = new List(); 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /PluginBase/Models/IBatchRegistry.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace PluginBase.Models 4 | { 5 | public delegate BatchProcessResults HfBatchOperation(BatchJob batchJob); 6 | public delegate void HfBatchLoad(IFormFile fileData); 7 | 8 | 9 | public interface IBatchRegistry 10 | { 11 | HfBatchOperation BatchEnqueue { get; } 12 | HfBatchOperation BatchExecute { get; } 13 | HfBatchLoad LoadBatch { get; } 14 | BatchDetails BatchDetails { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /PluginBase/Models/IJobExecutionPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using System; 5 | 6 | namespace PluginBase.Models 7 | { 8 | public interface IJobExecutionPlugin 9 | { 10 | 11 | void ConfigureServices(IServiceCollection services); 12 | 13 | void Configure(IApplicationBuilder app, IHostEnvironment env, IServiceProvider serviceProvider); 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /PluginBase/Models/JobTaskAggregator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PluginBase.Models 5 | { 6 | public class JobTaskAggregator 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Description { get; set; } 11 | 12 | public DateTime CreatedOn { get; set; } = DateTime.Now; 13 | 14 | public DateTime? StartedOn { get; set; } 15 | 16 | public DateTime? LastUpdateOn { get; set; } 17 | 18 | public DateTime? CompletedOn { get; set; } 19 | 20 | public Dictionary Properties { get; set; } = new Dictionary(); 21 | 22 | public List Steps { get; set; } = new List(); 23 | 24 | public string? Errors { get; set; } 25 | 26 | public List History { get; set; } 27 | 28 | public BatchJob? JobOwner { get; set; } 29 | 30 | public int? JobOwnerId { get; set; } 31 | 32 | public int TotalSteps { get; set; } = 0; 33 | 34 | public int StepsCompleted { get; set; } = 0; 35 | 36 | public string? ActiveStep { get; set; } 37 | 38 | public void Update() 39 | { 40 | } 41 | 42 | // TODO 43 | // public void SetProperty(DbContext context, string key, T value) 44 | //where T : struct 45 | // { 46 | // { 47 | // Properties[key] = value!.ToString(); 48 | // } 49 | // context.Entry(this).Property(d => d.Properties).IsModified = true; 50 | // } 51 | 52 | // public void SetProperty(DbContext context, string key, string value) 53 | 54 | // { 55 | // { 56 | // Properties[key] = value!.ToString(); 57 | // } 58 | // context.Entry(this).Property(d => d.Properties).IsModified = true; 59 | // } 60 | 61 | public string? GetProperty(string key) 62 | { 63 | if (Properties.ContainsKey(key)) 64 | { 65 | var st = Properties[key]; 66 | return st.ToString(); 67 | } 68 | return null; 69 | } 70 | 71 | public T? GetProperty(string key) 72 | where T : struct 73 | { 74 | if (Properties.ContainsKey(key)) 75 | { 76 | var st = Properties[key]; 77 | if (string.IsNullOrEmpty(st)) 78 | { 79 | return default(T); 80 | } 81 | try 82 | { 83 | return (T)Convert.ChangeType(st, typeof(T)); 84 | } 85 | catch 86 | { 87 | return default(T); 88 | } 89 | } 90 | return default(T); ; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /PluginBase/Models/StepHistory.cs: -------------------------------------------------------------------------------- 1 | namespace PluginBase.Models 2 | { 3 | public class StepHistory 4 | { 5 | public int Id { get; set; } 6 | public int StepNumber { get; set; } 7 | public string? StepResult { get; set; } 8 | public string? Error { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /PluginBase/Models/TaskStep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PluginBase.Models 4 | { 5 | public class TaskStep 6 | { 7 | public int Id { get; set; } 8 | public int AggregatorId { get; set; } 9 | 10 | public string StepType { get; set; } 11 | 12 | public int StepOrder { get; set; } 13 | 14 | public string Description { get; set; } 15 | 16 | public string? Data { get; set; } 17 | 18 | public DateTime? StartedOn { get; set; } 19 | 20 | public DateTime? CompletedOn { get; set; } 21 | 22 | public string? ErrorMessage { get; set; } 23 | 24 | public TimeSpan? ExecutionTime { get; set; } 25 | 26 | public double? ExecutionRate { get; set; } 27 | 28 | public int Retries { get; set; } = 1; 29 | 30 | public int RetryCount { get; set; } = 0; 31 | 32 | public string? Notes { get; set; } 33 | } 34 | } -------------------------------------------------------------------------------- /PluginBase/PluginBase.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /TestPlugin/Batches/TestDownloadBatch.cs: -------------------------------------------------------------------------------- 1 | using ApsSettings.Data; 2 | using Ganss.Excel; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Newtonsoft.Json; 6 | using PluginBase.Models; 7 | using TestPlugin.Controllers; 8 | using TestPlugin.Models; 9 | using TestPlugin.Plugin; 10 | 11 | namespace MigrationPlugin.Batches 12 | { 13 | internal class TestDownloadBatch : IBatchRegistry 14 | { 15 | public string Name => "Test Download"; 16 | 17 | public HfBatchOperation BatchExecute => throw new NotImplementedException(); 18 | 19 | public HfBatchOperation BuildStack => throw new NotImplementedException(); 20 | 21 | public HfBatchLoad LoadBatch { get => ProcessBatch; } 22 | 23 | public HfBatchOperation BatchEnqueue => EnqueueStart; 24 | 25 | public BatchDetails BatchDetails => new BatchDetails 26 | { 27 | Title = "Test 1", 28 | Key = "test_1_v1", 29 | Description = "Test of a plugin", 30 | BatchLoadUrl = TestBatchController.PATH_LOAD, 31 | BatchTemplateUrl = TestBatchController.PATH_TEMPLATE 32 | }; 33 | 34 | protected void ProcessBatch(IFormFile fileData) 35 | { 36 | string name = fileData.FileName; 37 | string extension = Path.GetExtension(fileData.FileName); 38 | 39 | using (var memoryStream = new MemoryStream()) 40 | { 41 | fileData.CopyTo(memoryStream); 42 | var items = new ExcelMapper(memoryStream).Fetch(); 43 | var context = TestPluginObj.Instance.ServiceProvider.GetService()!; 44 | 45 | foreach (var batchData in items) 46 | { 47 | var jb = new BatchJob 48 | { 49 | Name = batchData.Name, 50 | Data = JsonConvert.SerializeObject(batchData), 51 | Type = this.BatchDetails.Key 52 | }; 53 | 54 | context.Batches.Add(jb); 55 | } 56 | 57 | context.SaveChanges(); 58 | } 59 | } 60 | 61 | private BatchProcessResults EnqueueStart(BatchJob batchJob) 62 | { 63 | try 64 | { 65 | var context = TestPluginObj.Instance.ServiceProvider.GetService()!; 66 | } 67 | catch (Exception ex) 68 | { 69 | throw; 70 | } 71 | 72 | return null; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /TestPlugin/Controllers/TestBatchController.cs: -------------------------------------------------------------------------------- 1 | using Ac.Net.Authentication; 2 | using Ac.Net.Authentication.Models; 3 | 4 | using Flurl.Http; 5 | using Ganss.Excel; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.EntityFrameworkCore; 9 | 10 | using NPOI.SS.Formula.Functions; 11 | using System.IO; 12 | using System.Text; 13 | using TestPlugin.Excel; 14 | using TestPlugin.Models; 15 | 16 | namespace TestPlugin.Controllers 17 | { 18 | public class TestBatchController : Controller 19 | { 20 | const string operation = "test"; 21 | public const string PATH_TEMPLATE = "api/batch/" + operation + "/template"; 22 | public const string PATH_LOAD = "api/batch/" + operation + "/load"; 23 | [HttpGet] 24 | [Route(PATH_TEMPLATE)] 25 | public async Task GetTemplate() 26 | { 27 | try 28 | { 29 | var fileName = $"{ExcelUtils.DownloadWorksheet}.Template.{DateTime.Now.ToString("yy.MM.dd.mm.ss")}.xlsx"; 30 | var path = Path.Combine(Path.GetTempPath(), fileName); 31 | var bytes = ExcelUtils.MockTestDownloadXlsx(); 32 | return File(bytes, "application/vnd.ms-excel"); 33 | 34 | } 35 | catch (Exception ex) 36 | { 37 | return BadRequest(); 38 | } 39 | } 40 | 41 | [HttpPost] 42 | [Route(PATH_LOAD)] 43 | public async Task UploadData([FromForm] IFormFile FileData) 44 | { 45 | 46 | try 47 | { 48 | string name = FileData.FileName; 49 | string extension = Path.GetExtension(FileData.FileName); 50 | 51 | using (var memoryStream = new MemoryStream()) 52 | { 53 | FileData.CopyTo(memoryStream); 54 | var items = new ExcelMapper(memoryStream).Fetch(); 55 | 56 | 57 | 58 | //await BatchFlows.ProcessDownloadBatch(ThreeLeggedMananger.Instance, DataContext context, items); 59 | } 60 | 61 | return Ok(); 62 | 63 | } 64 | catch (Exception ex) 65 | { 66 | return BadRequest(); 67 | } 68 | } 69 | 70 | 71 | 72 | } 73 | } 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /TestPlugin/ExelUtils/ExcelUtils.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using Ganss.Excel; 3 | using TestPlugin.Models; 4 | 5 | namespace TestPlugin.Excel 6 | { 7 | public class ExcelUtils 8 | { 9 | public const string UploadFile = "Test.Upload"; 10 | 11 | public const string UploadWorksheet = "TestUploads"; 12 | 13 | public const string DownloadFile = "Test.Download"; 14 | 15 | public const string DownloadWorksheet = "TestDownloads"; 16 | 17 | public static TestDownload MockTestDownload(int index) 18 | { 19 | var mock = new Faker() 20 | .StrictMode(true) 21 | .RuleFor(o => o.SourceProject, f => f.Internet.Url()) 22 | .RuleFor(o => o.SourceFolderId, f => f.Internet.Url()) 23 | .RuleFor(o => o.DownloadFolder, f => $"c:\\temp\\{index.ToString("000")}") 24 | .RuleFor(o => o.IgnoreExtensions, f => f.System.FileExt()) 25 | .RuleFor(o => o.IgnoreFolders, f => f.Lorem.Word()) 26 | .RuleFor(o => o.SourceHub, f => f.Internet.Url()) 27 | .RuleFor(o => o.UseProjectForRoot, f => true) 28 | .RuleFor(o => o.Name, "TestBatch_" + index.ToString()); 29 | return mock; 30 | } 31 | 32 | 33 | 34 | public static byte[] MockTestDownloadXlsx() 35 | { 36 | try 37 | { 38 | var list = new List(); 39 | for (int i = 1; i < 10; i++) 40 | { 41 | list.Add(MockTestDownload(i)); 42 | } 43 | 44 | 45 | using (var stream = new MemoryStream()) 46 | { 47 | new ExcelMapper().Save(stream, list, "TestDownload"); 48 | return stream.ToArray(); 49 | } 50 | } 51 | catch (Exception ex) 52 | { 53 | Serilog.Log.Warning("Template download", ex); 54 | throw; 55 | } 56 | } 57 | 58 | } 59 | } -------------------------------------------------------------------------------- /TestPlugin/Models/TestDownload.cs: -------------------------------------------------------------------------------- 1 | using Ganss.Excel; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TestPlugin.Models 9 | { 10 | public class TestDownload 11 | { 12 | [Column("Name")] 13 | public string Name { get; set; } 14 | 15 | [Column("ID of Hub")] 16 | public string SourceHub { get; set; } 17 | 18 | [Column("ID of Project")] 19 | public string SourceProject { get; set; } 20 | 21 | [Column("Id to Target Folder (optional)")] 22 | public string SourceFolderId { get; set; } 23 | 24 | [Column("Root Folder for Download")] 25 | public string DownloadFolder { get; set; } 26 | 27 | [Column("Use Project For Root")] 28 | public bool UseProjectForRoot { get; set; } = false; 29 | 30 | [Column("File Extensions to Ignore (comma seperated)")] 31 | public string? IgnoreExtensions { get; set; } 32 | 33 | [Column("Folders to Ignore (comma seperated)")] 34 | public string? IgnoreFolders { get; set; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TestPlugin/Plugin/TestPluginObj.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using NPOI.SS.Formula.Functions; 5 | using PluginBase; 6 | using PluginBase.Models; 7 | 8 | namespace TestPlugin.Plugin 9 | { 10 | public class TestPluginObj : IJobExecutionPlugin 11 | { 12 | public TestPluginObj() 13 | { 14 | if (_instance != null) 15 | throw new NotSupportedException($"Only one instance of the TestPluginObj plugin is supported"); 16 | _instance = this; 17 | } 18 | public static TestPluginObj Instance => _instance; 19 | private static TestPluginObj _instance; 20 | 21 | public IServiceProvider ServiceProvider { get; private set; } 22 | 23 | 24 | public void Configure(IApplicationBuilder app, IHostEnvironment env, IServiceProvider serviceProvider) 25 | { 26 | ServiceProvider = serviceProvider; 27 | ISettingsProvider sp = ServiceProvider.GetService()!; 28 | 29 | 30 | } 31 | 32 | public void ConfigureServices(IServiceCollection services) 33 | { 34 | } 35 | 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /TestPlugin/TestPlugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------