SharedHtmlLocalizer
7 |
8 | @model ErrorViewModel
9 |
10 | @{
11 | ViewData["Title"] = SharedLocalizer[LocalizationConstants.ERROR];
12 | }
13 |
14 | @SharedLocalizer[LocalizationConstants.ERROR].
15 | @SharedLocalizer[LocalizationConstants.AN_ERROR_OCCURED_HEADER]
16 |
17 | @if (Model?.ShowRequestId ?? false)
18 | {
19 |
20 | @SharedLocalizer[LocalizationConstants.REQUEST] ID: @Model?.RequestId
21 |
22 | }
23 |
24 | @SharedLocalizer[LocalizationConstants.DEVELOPMENT_MODE]
25 |
26 | @SharedHtmlLocalizer[LocalizationConstants.SWAPPING_TO_DEVELOPMENT_MODE_DISPLAY]
27 |
28 |
29 | @SharedHtmlLocalizer[LocalizationConstants.DETAILED_ERROR_MESSAGE]
30 |
31 |
--------------------------------------------------------------------------------
/src/BulkRename/Helpers/FolderHelper.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.Helpers
2 | {
3 | using BulkRename.Constants;
4 |
5 | internal static class FolderHelper
6 | {
7 | internal static string GetRootFolder()
8 | {
9 | var currentDirectory = Directory.GetCurrentDirectory();
10 | var rootFolder = Path.Combine(currentDirectory, EnvironmentConstants.FILES_ROOT_PATH_FOLDER_NAME);
11 | return rootFolder;
12 | }
13 |
14 | internal static string GetHistoryFileName()
15 | {
16 | var currentDirectory = Directory.GetCurrentDirectory();
17 | var historyFolder = Path.Combine(currentDirectory, EnvironmentConstants.FILES_HISTORY_PATH_FOLDER_NAME);
18 | if (!Directory.Exists(historyFolder))
19 | {
20 | Directory.CreateDirectory(historyFolder);
21 | }
22 |
23 | var historyFileName = Path.Combine(historyFolder, EnvironmentConstants.FILES_HISTORY_FILE_NAME);
24 |
25 | return historyFileName;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/BulkRename/wwwroot/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Ramazan Yilmaz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726134350_AddColumnsEpsEpisodeOriginalNameAndEpsEpisodeNewName.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace BulkRename.Migrations
4 | {
5 | public partial class AddColumnsEpsEpisodeOriginalNameAndEpsEpisodeNewName : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.AddColumn(
10 | name: "EpsEpisodeNewName",
11 | table: "EpisodeItems",
12 | nullable: true);
13 |
14 | migrationBuilder.AddColumn(
15 | name: "EpsEpisodeOriginalName",
16 | table: "EpisodeItems",
17 | nullable: true);
18 | }
19 |
20 | protected override void Down(MigrationBuilder migrationBuilder)
21 | {
22 | migrationBuilder.DropColumn(
23 | name: "EpsEpisodeNewName",
24 | table: "EpisodeItems");
25 |
26 | migrationBuilder.DropColumn(
27 | name: "EpsEpisodeOriginalName",
28 | table: "EpisodeItems");
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/BulkRename/Controllers/HomeController.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.Controllers
2 | {
3 | using System.Diagnostics;
4 |
5 | using BulkRename.Models;
6 |
7 | using Microsoft.AspNetCore.Diagnostics;
8 | using Microsoft.AspNetCore.Mvc;
9 |
10 | public class HomeController : Controller
11 | {
12 | private readonly ILogger _logger;
13 |
14 | public HomeController(ILogger logger)
15 | {
16 | _logger = logger;
17 | }
18 |
19 | public IActionResult Index()
20 | {
21 | return View();
22 | }
23 |
24 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
25 | public IActionResult Error()
26 | {
27 | var exception = HttpContext.Features.Get()?.Error;
28 | _logger.LogError(exception, "Something went wrong!");
29 | return View(
30 | new ErrorViewModel
31 | {
32 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
33 | });
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125357_CreateTableRenamingSessionItems.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace BulkRename.Migrations
5 | {
6 | public partial class CreateTableRenamingSessionItems : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "RenamingSessionItems",
12 | columns: table => new
13 | {
14 | RenamingSessionID = table.Column(nullable: false),
15 | RenName = table.Column(nullable: true),
16 | RenExecutingDateTime = table.Column(nullable: false)
17 | },
18 | constraints: table =>
19 | {
20 | table.PrimaryKey("PK_RenamingSessionItems", x => x.RenamingSessionID);
21 | });
22 | }
23 |
24 | protected override void Down(MigrationBuilder migrationBuilder)
25 | {
26 | migrationBuilder.DropTable(
27 | name: "RenamingSessionItems");
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1.2.0
4 |
5 | Ramazan Yilmaz
6 | Copyright © Ramazan Yilmaz, $([System.DateTime]::Now.ToString(yyyy))
7 |
8 | net8.0
9 | enable
10 | enable
11 |
12 |
13 | MIT
14 |
15 |
16 | git
17 | https://github.com/Ramo-Y/BulkRename
18 |
19 | latest
20 |
21 | Linux
22 |
23 |
24 |
25 | TRACE;RELEASE
26 | none
27 | false
28 |
29 |
30 |
31 | DEBUG;TRACE
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/BulkRename/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 |
--------------------------------------------------------------------------------
/src/BulkRename/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 |
--------------------------------------------------------------------------------
/src/BulkRename/Views/Shared/_Layout.cshtml.css:
--------------------------------------------------------------------------------
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 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | a {
11 | color: #0077cc;
12 | }
13 |
14 | .btn-primary {
15 | color: #fff;
16 | background-color: #1b6ec2;
17 | border-color: #1861ac;
18 | }
19 |
20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
21 | color: #fff;
22 | background-color: #1b6ec2;
23 | border-color: #1861ac;
24 | }
25 |
26 | .border-top {
27 | border-top: 1px solid #e5e5e5;
28 | }
29 | .border-bottom {
30 | border-bottom: 1px solid #e5e5e5;
31 | }
32 |
33 | .box-shadow {
34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
35 | }
36 |
37 | button.accept-policy {
38 | font-size: 1rem;
39 | line-height: inherit;
40 | }
41 |
42 | .footer {
43 | border-top: 1px solid #ccc;
44 | position: absolute;
45 | bottom: 0;
46 | width: 100%;
47 | white-space: nowrap;
48 | line-height: 60px;
49 | }
50 |
51 | .footer-links {
52 | display: flex;
53 | justify-content: space-evenly;
54 | align-items: center;
55 | }
56 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | on:
2 | workflow_call:
3 | inputs:
4 | image_version:
5 | type: string
6 | required: true
7 | outputs:
8 | image_version:
9 | value: ${{ inputs.image_version }}
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 | - name: Setup .NET
18 | uses: actions/setup-dotnet@v4
19 | with:
20 | dotnet-version: 8.0.x
21 | - name: Restore dependencies
22 | run: dotnet restore
23 | working-directory: src
24 | - name: Build
25 | run: dotnet build --no-restore
26 | working-directory: src
27 | - name: Set up Docker Buildx
28 | uses: docker/setup-buildx-action@v3
29 | - name: Docker build and push
30 | uses: docker/build-push-action@v5
31 | with:
32 | context: ./src
33 | file: ./src/Dockerfile
34 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ vars.IMAGE_NAME }}:${{ inputs.image_version }}
35 | outputs: type=docker,dest=/tmp/${{ vars.IMAGE_NAME }}.tar
36 | - name: Upload artifact
37 | uses: actions/upload-artifact@v4
38 | with:
39 | name: ${{ vars.IMAGE_NAME }}
40 | path: /tmp/${{ vars.IMAGE_NAME }}.tar
41 |
--------------------------------------------------------------------------------
/src/BulkRename/Constants/LocalizationConstants.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.Constants
2 | {
3 | public static class LocalizationConstants
4 | {
5 | public const string AN_ERROR_OCCURED_HEADER = "AnErrorOccuredHeader";
6 |
7 | public const string DETAILED_ERROR_MESSAGE = "DetailedErrorMessage";
8 |
9 | public const string DEVELOPMENT_MODE = "DevelopmentMode";
10 |
11 | public const string ERROR = "Error";
12 |
13 | public const string HISTORY = "History";
14 |
15 | public const string HOME = "Home";
16 |
17 | public const string LOAD_HISTORY = "LoadHistory";
18 |
19 | public const string NEW_NAME = "NewName";
20 |
21 | public const string OLD_NAME = "OldName";
22 |
23 | public const string PREVIEW_RENAMING_OF_TV_SHOWS = "PreviewRenamingOfTvShows";
24 |
25 | public const string RENAMED_ON = "RenamedOn";
26 |
27 | public const string REQUEST = "Request";
28 |
29 | public const string SERIES = "Series";
30 |
31 | public const string SUBMIT_RENAMING = "Submit Renaming";
32 |
33 | public const string SUCCESSFULLY_RENAMED_FILES = "SuccessfullyRenamedFiles";
34 |
35 | public const string SWAPPING_TO_DEVELOPMENT_MODE_DISPLAY = "SwappingToDevelopmentModeDisplay";
36 |
37 | public const string WELCOME_TO_BULK_RENAME = "WelcomeToBulkRename";
38 | }
39 | }
--------------------------------------------------------------------------------
/src/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 | mssqlserver:
5 | image: mcr.microsoft.com/mssql/server:2022-latest
6 | container_name: "mssqlserver"
7 | environment:
8 | ACCEPT_EULA: "Y"
9 | SA_PASSWORD: "${SQL_SERVER_SA_PASSWORD}"
10 | MSSQL_PID: Express
11 | TZ: "Europe/Zurich"
12 | ports:
13 | - "${SQL_SERVER_EXTERNAL_PORT}:1433"
14 | bulkrename:
15 | depends_on:
16 | - "mssqlserver"
17 | image: ${DOCKER_REGISTRY}bulkrename:${VERSION}
18 | container_name: "bulkrename"
19 | environment:
20 | ASPNETCORE_URLS: http://*:8080
21 | PersistanceMode: "${PERSITANCE_MODE}"
22 | DbServer: "mssqlserver"
23 | DbName: "${DB_NAME}"
24 | DbPort: "1433"
25 | DbUser: "${DB_USER}"
26 | DbPassword: "${SQL_SERVER_SA_PASSWORD}"
27 | DbConnectionTimeOutInSeconds: "60"
28 | SupportedFileEndings: "${SUPPORTED_FILE_ENDINGS}"
29 | FoldersToIgnore: "${FOLDERS_TO_IGNORE}"
30 | SeqUrl: "${SEQ_URL}"
31 | SeqApiKey: "${SEQ_API_KEY}"
32 | TZ: "Europe/Zurich"
33 | ports:
34 | - "${BULK_RENAME_PORT}:8080"
35 | volumes:
36 | - "${BULK_RENAME_FOLDER}://app//Files//"
37 | - "${LOG_FOLDER}://app//Logs//"
38 | - "${HISTORY_FOLDER}://app//RenamingHistory//"
39 | healthcheck:
40 | test: wget -qO - http://localhost:8080/healthz/ || exit 1
41 | interval: 10s
42 | timeout: 5s
43 | retries: 10
44 | start_period: 10s
45 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125059_CreateTableSerie.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using BulkRename.Models.Entities;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace BulkRename.Migrations
11 | {
12 | [DbContext(typeof(BulkRenameContext))]
13 | [Migration("20200726125059_CreateTableSerie")]
14 | partial class CreateTableSerie
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.1.6")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Serie", b =>
25 | {
26 | b.Property("SerieID")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("SerName")
31 | .HasColumnType("nvarchar(max)");
32 |
33 | b.HasKey("SerieID");
34 |
35 | b.ToTable("SerieItems");
36 | });
37 | #pragma warning restore 612, 618
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "BulkRename",
3 | "projectOwner": "Ramo-Y",
4 | "files": [
5 | "README.md"
6 | ],
7 | "commitType": "docs",
8 | "commitConvention": "angular",
9 | "contributorsPerLine": 7,
10 | "contributors": [
11 | {
12 | "login": "sszemtelen",
13 | "name": "Roland.",
14 | "avatar_url": "https://avatars.githubusercontent.com/u/114315281?v=4",
15 | "profile": "https://github.com/sszemtelen",
16 | "contributions": [
17 | "translation"
18 | ]
19 | },
20 | {
21 | "login": "Ramo-Y",
22 | "name": "Ramazan Yilmaz",
23 | "avatar_url": "https://avatars.githubusercontent.com/u/34425244?v=4",
24 | "profile": "https://github.com/Ramo-Y",
25 | "contributions": [
26 | "code"
27 | ]
28 | },
29 | {
30 | "login": "all-contributors",
31 | "name": "All Contributors",
32 | "avatar_url": "https://avatars.githubusercontent.com/u/46410174?v=4",
33 | "profile": "https://allcontributors.org",
34 | "contributions": [
35 | "doc"
36 | ]
37 | },
38 | {
39 | "login": "hetlelid",
40 | "name": "hetlelid",
41 | "avatar_url": "https://avatars.githubusercontent.com/u/279584?v=4",
42 | "profile": "https://github.com/hetlelid",
43 | "contributions": [
44 | "doc"
45 | ]
46 | },
47 | {
48 | "login": "qsig-a",
49 | "name": "Andrew Bell",
50 | "avatar_url": "https://avatars.githubusercontent.com/u/26676295?v=4",
51 | "profile": "https://github.com/qsig-a",
52 | "contributions": [
53 | "bug"
54 | ]
55 | }
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125210_CreateTableSeason.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace BulkRename.Migrations
5 | {
6 | public partial class CreateTableSeason : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "SeasonItems",
12 | columns: table => new
13 | {
14 | SeasonID = table.Column(nullable: false),
15 | SsnNumberString = table.Column(nullable: true),
16 | SsnSerieID_FK = table.Column(nullable: false)
17 | },
18 | constraints: table =>
19 | {
20 | table.PrimaryKey("PK_SeasonItems", x => x.SeasonID);
21 | table.ForeignKey(
22 | name: "FK_SeasonItems_SerieItems_SsnSerieID_FK",
23 | column: x => x.SsnSerieID_FK,
24 | principalTable: "SerieItems",
25 | principalColumn: "SerieID",
26 | onDelete: ReferentialAction.Cascade);
27 | });
28 |
29 | migrationBuilder.CreateIndex(
30 | name: "IX_SeasonItems_SsnSerieID_FK",
31 | table: "SeasonItems",
32 | column: "SsnSerieID_FK");
33 | }
34 |
35 | protected override void Down(MigrationBuilder migrationBuilder)
36 | {
37 | migrationBuilder.DropTable(
38 | name: "SeasonItems");
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125315_CreateTableEpisodeItems.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace BulkRename.Migrations
5 | {
6 | public partial class CreateTableEpisodeItems : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "EpisodeItems",
12 | columns: table => new
13 | {
14 | EpisodeID = table.Column(nullable: false),
15 | EpsNumberString = table.Column(nullable: true),
16 | EpsSeasonID_FK = table.Column(nullable: false)
17 | },
18 | constraints: table =>
19 | {
20 | table.PrimaryKey("PK_EpisodeItems", x => x.EpisodeID);
21 | table.ForeignKey(
22 | name: "FK_EpisodeItems_SeasonItems_EpsSeasonID_FK",
23 | column: x => x.EpsSeasonID_FK,
24 | principalTable: "SeasonItems",
25 | principalColumn: "SeasonID",
26 | onDelete: ReferentialAction.Cascade);
27 | });
28 |
29 | migrationBuilder.CreateIndex(
30 | name: "IX_EpisodeItems_EpsSeasonID_FK",
31 | table: "EpisodeItems",
32 | column: "EpsSeasonID_FK");
33 | }
34 |
35 | protected override void Down(MigrationBuilder migrationBuilder)
36 | {
37 | migrationBuilder.DropTable(
38 | name: "EpisodeItems");
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/BulkRename/Services/DatabasePersistanceService.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.Services
2 | {
3 | using BulkRename.Interfaces;
4 | using BulkRename.Models.Entities;
5 | using Microsoft.EntityFrameworkCore;
6 |
7 | public class DatabasePersistanceService : IPersistanceService
8 | {
9 | private readonly BulkRenameContext _context;
10 |
11 | public DatabasePersistanceService(BulkRenameContext context)
12 | {
13 | _context = context;
14 | }
15 |
16 | public async Task PersistRenamingInformation(IEnumerable renamingSessionToEpisodes, CancellationToken cancellationToken = default)
17 | {
18 | if (renamingSessionToEpisodes.Any())
19 | {
20 | await _context.RenamingSessionToEpisodeItems.AddRangeAsync(renamingSessionToEpisodes, cancellationToken);
21 | await _context.SaveChangesAsync(cancellationToken);
22 | await _context.DisposeAsync();
23 | }
24 | }
25 |
26 | public async Task> LoadRenamingHistory(CancellationToken cancellationToken = default)
27 | {
28 | var renamingSessionToEpisodes = await _context.RenamingSessionToEpisodeItems
29 | .Include(x => x.Episode)
30 | .Include(x => x.RenamingSession)
31 | .Include(x => x.Episode.Season)
32 | .Include(x => x.Episode.Season.Serie)
33 | .AsNoTracking()
34 | .ToListAsync(cancellationToken);
35 |
36 | await _context.DisposeAsync();
37 |
38 | return renamingSessionToEpisodes;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/src/BulkRename/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 |
--------------------------------------------------------------------------------
/.github/workflows/set_version.yml:
--------------------------------------------------------------------------------
1 | on:
2 | workflow_call:
3 | outputs:
4 | image_version:
5 | value: ${{ jobs.set_version.outputs.image_version }}
6 |
7 | jobs:
8 | set_version:
9 | outputs:
10 | image_version: ${{ steps.set-version.outputs.image_version }}
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 | - name: Set Version
16 | id: set-version
17 | run: |
18 | if [[ "${{ github.ref }}" == refs/heads/master ]]; then
19 | echo "image_version=latest" >> $GITHUB_OUTPUT
20 |
21 | elif [[ "${{ github.ref }}" == refs/heads/develop ]]; then
22 | echo "image_version=latest-develop" >> $GITHUB_OUTPUT
23 |
24 | elif [[ "${{ github.ref }}" == refs/heads/release/* ]]; then
25 | str="${{ github.ref }}"
26 | str=${str:19}
27 | echo "image_version=$str" >> $GITHUB_OUTPUT
28 |
29 | elif [[ "${{ github.ref }}" == refs/heads/feature/* ]]; then
30 | str="${{ github.ref }}"
31 | str=${str:19}
32 | echo "image_version=$str" >> $GITHUB_OUTPUT
33 |
34 | elif [[ "${{ github.ref }}" == refs/heads/bugfix/* ]]; then
35 | str="${{ github.ref }}"
36 | str=${str:18}
37 | echo "image_version=$str" >> $GITHUB_OUTPUT
38 |
39 | elif [[ "${{ github.ref }}" == refs/heads/experimental/* ]]; then
40 | str="${{ github.ref }}"
41 | str=${str:24}
42 | echo "image_version=$str" >> $GITHUB_OUTPUT
43 |
44 | else
45 | echo "image_version=latest" >> $GITHUB_OUTPUT
46 | fi
47 | - name: Show image version
48 | run: echo "Image version is '${{ steps.set-version.outputs.image_version }}'"
49 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on:
2 | workflow_call:
3 | inputs:
4 | image_version:
5 | type: string
6 | required: true
7 | outputs:
8 | image_version:
9 | value: ${{ inputs.image_version }}
10 |
11 | jobs:
12 | test:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 | - name: Create temp directories
18 | run: |
19 | mkdir ${{ runner.temp }}/BulkRename
20 | mkdir ${{ runner.temp }}/BulkRename/Files
21 | mkdir ${{ runner.temp }}/BulkRename/Logs
22 | mkdir ${{ runner.temp }}/BulkRename/RenamingHistory
23 | - name: Create Env
24 | shell: pwsh
25 | run: ./CreateEnvFile.ps1 -PersistanceMode "Database" -SqlServerSaPassword ${{ secrets.SQL_SERVER_SA_PASSWORD }} -BulkRenameFolder "${{ runner.temp }}/BulkRename/Files" -HistoryFolder "${{ runner.temp }}/BulkRename/RenamingHistory" -LogFolder "${{ runner.temp }}/BulkRename/Logs" -Version ${{ inputs.image_version }}
26 | working-directory: src
27 | - name: Set up Docker Buildx
28 | uses: docker/setup-buildx-action@v3
29 | - name: Download artifact
30 | uses: actions/download-artifact@v4
31 | with:
32 | name: ${{ vars.IMAGE_NAME }}
33 | path: /tmp
34 | - name: Load Docker image
35 | run: |
36 | docker load --input /tmp/${{ vars.IMAGE_NAME }}.tar
37 | docker image ls -a
38 | - name: Docker Cmpose
39 | shell: pwsh
40 | run: docker compose up -d --wait --wait-timeout 60
41 | working-directory: src
42 | - name: Integration Tests
43 | run: dotnet test src/**/*.IntegrationTests.csproj
44 | - name: Upload logs on fail
45 | if: ${{ failure() }}
46 | uses: actions/upload-artifact@v4
47 | with:
48 | name: Build failure logs
49 | path: ${{ runner.temp }}/BulkRename/Logs
50 |
--------------------------------------------------------------------------------
/src/BulkRename.IntegrationTests/Helpers/ConfigurationHelper.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.IntegrationTests.Helpers
2 | {
3 | using BulkRename.IntegrationTests.Constants;
4 |
5 | internal static class ConfigurationHelper
6 | {
7 | internal static void SetEnvironmentVariables()
8 | {
9 | var filePath = GetEnvironmentFilePath();
10 | var allLines = File.ReadAllLines(filePath);
11 |
12 | foreach (var line in allLines)
13 | {
14 | var parts = line.Split('=', StringSplitOptions.RemoveEmptyEntries);
15 |
16 | if (parts.Length != 2)
17 | {
18 | continue;
19 | }
20 |
21 | Environment.SetEnvironmentVariable(parts[0], parts[1]);
22 | }
23 | }
24 |
25 | internal static string GetContainerExternalPort()
26 | {
27 | var port = Environment.GetEnvironmentVariable(ConfigurationConstants.BULK_RENAME_PORT)!;
28 | return port;
29 | }
30 |
31 | internal static string GetContainerMappedFilesFolderPath()
32 | {
33 | var folder = Environment.GetEnvironmentVariable(ConfigurationConstants.BULK_RENAME_FOLDER)!;
34 | return folder;
35 | }
36 |
37 | internal static string[] GetSupportedFileEndings()
38 | {
39 | var fileEndings = Environment.GetEnvironmentVariable(ConfigurationConstants.SUPPORTED_FILE_ENDINGS)!.Split(';');
40 | return fileEndings;
41 | }
42 |
43 | private static string GetEnvironmentFilePath()
44 | {
45 | var currentDirectory = Directory.GetCurrentDirectory();
46 | var solutionFolder = Directory.GetParent(currentDirectory)!.Parent?.Parent?.Parent?.ToString();
47 | var envFilePath = Path.Combine(solutionFolder!, ConfigurationConstants.ENV_FILE_NAME);
48 | return envFilePath;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/BulkRename.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31919.166
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0B6E7D2A-D37D-42F6-A96C-C3A4687EA13A}"
7 | ProjectSection(SolutionItems) = preProject
8 | .dockerignore = .dockerignore
9 | ..\.gitignore = ..\.gitignore
10 | ..\DEVELOPMENT.md = ..\DEVELOPMENT.md
11 | Directory.Build.props = Directory.Build.props
12 | Dockerfile = Dockerfile
13 | ..\README.md = ..\README.md
14 | EndProjectSection
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BulkRename", "BulkRename\BulkRename.csproj", "{92E7B662-F8C8-453B-A4E8-C5BFA09126BB}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BulkRename.IntegrationTests", "BulkRename.IntegrationTests\BulkRename.IntegrationTests.csproj", "{0F3F86EB-1BA2-499A-B48D-39E1E3DE665E}"
19 | EndProject
20 | Global
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|Any CPU = Debug|Any CPU
23 | Release|Any CPU = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
26 | {92E7B662-F8C8-453B-A4E8-C5BFA09126BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {92E7B662-F8C8-453B-A4E8-C5BFA09126BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {92E7B662-F8C8-453B-A4E8-C5BFA09126BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {92E7B662-F8C8-453B-A4E8-C5BFA09126BB}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {0F3F86EB-1BA2-499A-B48D-39E1E3DE665E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {0F3F86EB-1BA2-499A-B48D-39E1E3DE665E}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {0F3F86EB-1BA2-499A-B48D-39E1E3DE665E}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {0F3F86EB-1BA2-499A-B48D-39E1E3DE665E}.Release|Any CPU.Build.0 = Release|Any CPU
34 | EndGlobalSection
35 | GlobalSection(SolutionProperties) = preSolution
36 | HideSolutionNode = FALSE
37 | EndGlobalSection
38 | GlobalSection(ExtensibilityGlobals) = postSolution
39 | SolutionGuid = {E4BAD450-4293-4327-825F-63EC09B51A99}
40 | EndGlobalSection
41 | EndGlobal
42 |
--------------------------------------------------------------------------------
/src/BulkRename/Program.cs:
--------------------------------------------------------------------------------
1 | using BulkRename;
2 | using Microsoft.AspNetCore.Localization;
3 | using Microsoft.AspNetCore.Mvc.Razor;
4 | using Serilog;
5 | using System.Globalization;
6 | using System.Reflection;
7 |
8 | var builder = WebApplication.CreateBuilder(args);
9 | builder.Host.UseSerilog();
10 |
11 | var startup = new Startup(builder.Configuration);
12 | startup.ConfigureServices(builder.Services);
13 |
14 | // Add services to the container.
15 | builder.Services.AddControllersWithViews()
16 | .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
17 |
18 | builder.Services.AddLocalization(options =>
19 | {
20 | options.ResourcesPath = "Resources";
21 | });
22 | const string defaultCulture = "en";
23 |
24 | var supportedCultures = new[]
25 | {
26 | new CultureInfo(defaultCulture),
27 | new CultureInfo("hu"),
28 | new CultureInfo("de")
29 | };
30 |
31 | builder.Services.Configure(options => {
32 | options.DefaultRequestCulture = new RequestCulture(defaultCulture);
33 | options.SupportedCultures = supportedCultures;
34 | options.SupportedUICultures = supportedCultures;
35 | });
36 |
37 | builder.Services.AddRazorPages().AddDataAnnotationsLocalization(options =>
38 | {
39 | options.DataAnnotationLocalizerProvider = (type, factory) =>
40 | {
41 | var assemblyName = new AssemblyName(typeof(SharedResource)!.GetTypeInfo()!.Assembly!.FullName!);
42 |
43 | return factory.Create("SharedResource", assemblyName!.Name!);
44 | };
45 | });
46 |
47 | var app = builder.Build();
48 |
49 | app.UseRequestLocalization();
50 |
51 | // Configure the HTTP request pipeline.
52 | if (!app.Environment.IsDevelopment())
53 | {
54 | app.UseExceptionHandler("/Home/Error");
55 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
56 | app.UseHsts();
57 | }
58 |
59 | app.UseHttpsRedirection();
60 | app.UseStaticFiles();
61 |
62 | app.UseRouting();
63 |
64 | app.UseAuthorization();
65 |
66 | app.MapControllerRoute(
67 | name: "default",
68 | pattern: "{controller=Home}/{action=Index}/{id?}");
69 | await startup.Configure(app);
70 |
71 | app.Run();
72 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125452_CreateTableRenamingSessionToEpisodeItems.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace BulkRename.Migrations
5 | {
6 | public partial class CreateTableRenamingSessionToEpisodeItems : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "RenamingSessionToEpisodeItems",
12 | columns: table => new
13 | {
14 | RenamingSessionToEpisodeID = table.Column(nullable: false),
15 | RseRenamingSessionID_FK = table.Column(nullable: false),
16 | RseEpisodeID_FK = table.Column(nullable: false)
17 | },
18 | constraints: table =>
19 | {
20 | table.PrimaryKey("PK_RenamingSessionToEpisodeItems", x => x.RenamingSessionToEpisodeID);
21 | table.ForeignKey(
22 | name: "FK_RenamingSessionToEpisodeItems_EpisodeItems_RseEpisodeID_FK",
23 | column: x => x.RseEpisodeID_FK,
24 | principalTable: "EpisodeItems",
25 | principalColumn: "EpisodeID",
26 | onDelete: ReferentialAction.Cascade);
27 | table.ForeignKey(
28 | name: "FK_RenamingSessionToEpisodeItems_RenamingSessionItems_RseRenamingSessionID_FK",
29 | column: x => x.RseRenamingSessionID_FK,
30 | principalTable: "RenamingSessionItems",
31 | principalColumn: "RenamingSessionID",
32 | onDelete: ReferentialAction.Cascade);
33 | });
34 |
35 | migrationBuilder.CreateIndex(
36 | name: "IX_RenamingSessionToEpisodeItems_RseEpisodeID_FK",
37 | table: "RenamingSessionToEpisodeItems",
38 | column: "RseEpisodeID_FK");
39 |
40 | migrationBuilder.CreateIndex(
41 | name: "IX_RenamingSessionToEpisodeItems_RseRenamingSessionID_FK",
42 | table: "RenamingSessionToEpisodeItems",
43 | column: "RseRenamingSessionID_FK");
44 | }
45 |
46 | protected override void Down(MigrationBuilder migrationBuilder)
47 | {
48 | migrationBuilder.DropTable(
49 | name: "RenamingSessionToEpisodeItems");
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/BulkRename/Controllers/SeriesController.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.Controllers
2 | {
3 | using BulkRename.Interfaces;
4 | using BulkRename.Models;
5 | using BulkRename.Models.Entities;
6 | using Microsoft.AspNetCore.Mvc;
7 |
8 | public class SeriesController : Controller
9 | {
10 | private static readonly List _allFileAndFolderItemsFromRootFolder = [];
11 | private static readonly Dictionary> _dictionary = [];
12 |
13 | private readonly ILogger _logger;
14 | private readonly IFileService _fileService;
15 |
16 | public SeriesController(ILogger logger, IFileService fileService)
17 | {
18 | _logger = logger;
19 | _fileService = fileService;
20 | }
21 |
22 | public IActionResult Index()
23 | {
24 | _allFileAndFolderItemsFromRootFolder.Clear();
25 | _dictionary.Clear();
26 |
27 | _allFileAndFolderItemsFromRootFolder.AddRange(_fileService.GetAllFileAndFolderItemsFromRootFolder());
28 |
29 | var seasons = _allFileAndFolderItemsFromRootFolder.GroupBy(e => e.Season);
30 | foreach (var season in seasons)
31 | {
32 | var serieSerName = season.Key.Serie.SerName;
33 | var episodes = _allFileAndFolderItemsFromRootFolder.Where(e => e.Season.Equals(season.Key));
34 | var series = new List();
35 | foreach (var episode in episodes)
36 | {
37 | series.Add(
38 | new Series
39 | {
40 | OldName = episode.EpsEpisodeOriginalName,
41 | NewName = episode.EpsEpisodeNewName
42 | });
43 | }
44 |
45 | var seasonName = $"{serieSerName} - Season {season.Key.SsnNumberString}";
46 | _logger.LogInformation($"{seasonName} has {episodes.Count()} episodes to rename");
47 | _dictionary.Add(seasonName, series);
48 | }
49 |
50 | _logger.LogInformation($"{seasons.Count()} Seasons to rename", seasons);
51 |
52 | return View(_dictionary);
53 | }
54 |
55 | public async Task RenameAsync()
56 | {
57 | await _fileService.RenameSelectedEpisodeItems(_allFileAndFolderItemsFromRootFolder);
58 | return View("Rename", _dictionary);
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/BulkRename/Controllers/HistoryController.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.Controllers
2 | {
3 | using BulkRename.Constants;
4 | using BulkRename.Interfaces;
5 | using BulkRename.Models;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.Extensions.Localization;
8 |
9 | public class HistoryController : Controller
10 | {
11 | private readonly IPersistanceService _persistanceService;
12 |
13 | private static readonly Dictionary> _dictionary = [];
14 |
15 | private readonly string _renamedOn;
16 |
17 | public HistoryController(IPersistanceService persistanceService, IStringLocalizer sharedLocalizer)
18 | {
19 | _persistanceService = persistanceService;
20 |
21 | _renamedOn = sharedLocalizer[LocalizationConstants.RENAMED_ON];
22 | }
23 |
24 | public IActionResult Index()
25 | {
26 | return View(_dictionary);
27 | }
28 |
29 | public async Task LoadHistory()
30 | {
31 | _dictionary.Clear();
32 |
33 | var renamingSessionToEpisodes = await _persistanceService.LoadRenamingHistory();
34 |
35 | var seasons = renamingSessionToEpisodes.OrderByDescending(e => e.RenamingSession.RenExecutingDateTime)
36 | .GroupBy(e => e.Episode.Season.SeasonID);
37 | foreach (var season in seasons)
38 | {
39 | var renamingSessionToEpisode = renamingSessionToEpisodes.Where(x => x.Episode.EpsSeasonID_FK == season.Key)
40 | .First();
41 |
42 | var episodes = renamingSessionToEpisodes.Where(e => e.Episode.Season.SeasonID == season.Key)
43 | .OrderBy(x => x.Episode.EpsNumberString)
44 | .Select(x => x.Episode);
45 | var series = new List();
46 | foreach (var episode in episodes)
47 | {
48 | series.Add(
49 | new Series
50 | {
51 | OldName = episode.EpsEpisodeOriginalName,
52 | NewName = episode.EpsEpisodeNewName
53 | });
54 | }
55 |
56 | var key = $"{renamingSessionToEpisode.RenamingSession.RenName}, {_renamedOn}: {renamingSessionToEpisode.RenamingSession.RenExecutingDateTime:yyyy-MM-dd HH:mm:ss}";
57 | _dictionary.Add(key, series);
58 | }
59 |
60 | return RedirectToAction("Index");
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125210_CreateTableSeason.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using BulkRename.Models.Entities;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace BulkRename.Migrations
11 | {
12 | [DbContext(typeof(BulkRenameContext))]
13 | [Migration("20200726125210_CreateTableSeason")]
14 | partial class CreateTableSeason
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.1.6")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
25 | {
26 | b.Property("SeasonID")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("SsnNumberString")
31 | .HasColumnType("nvarchar(max)");
32 |
33 | b.Property("SsnSerieID_FK")
34 | .HasColumnType("uniqueidentifier");
35 |
36 | b.HasKey("SeasonID");
37 |
38 | b.HasIndex("SsnSerieID_FK");
39 |
40 | b.ToTable("SeasonItems");
41 | });
42 |
43 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Serie", b =>
44 | {
45 | b.Property("SerieID")
46 | .ValueGeneratedOnAdd()
47 | .HasColumnType("uniqueidentifier");
48 |
49 | b.Property("SerName")
50 | .HasColumnType("nvarchar(max)");
51 |
52 | b.HasKey("SerieID");
53 |
54 | b.ToTable("SerieItems");
55 | });
56 |
57 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
58 | {
59 | b.HasOne("BulkRename.Shared.Models.Entities.Serie", "Serie")
60 | .WithMany()
61 | .HasForeignKey("SsnSerieID_FK")
62 | .OnDelete(DeleteBehavior.Cascade)
63 | .IsRequired();
64 | });
65 | #pragma warning restore 612, 618
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/BulkRename/Services/JsonPersistanceService.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.Services
2 | {
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | using BulkRename.Helpers;
7 | using BulkRename.Interfaces;
8 | using BulkRename.Models.Entities;
9 |
10 | using Newtonsoft.Json;
11 |
12 | public class JsonPersistanceService : IPersistanceService
13 | {
14 | public async Task PersistRenamingInformation(IEnumerable renamingSessionToEpisodes, CancellationToken cancellationToken = default)
15 | {
16 | var serializeObject = string.Empty;
17 | var filePath = FolderHelper.GetHistoryFileName();
18 |
19 | var objectFromExistingJson = await GetHistoryFromFile(cancellationToken);
20 | objectFromExistingJson.AddRange(renamingSessionToEpisodes);
21 |
22 | await Task.Factory.StartNew(
23 | () =>
24 | {
25 | serializeObject = JsonConvert.SerializeObject(objectFromExistingJson, Formatting.Indented);
26 | }, cancellationToken);
27 |
28 | var bytes = Encoding.UTF8.GetBytes(serializeObject);
29 | await using (var sourceStream = new FileStream(
30 | filePath,
31 | FileMode.OpenOrCreate,
32 | FileAccess.Write,
33 | FileShare.None,
34 | 4096,
35 | true))
36 | {
37 | sourceStream.SetLength(0);
38 | await sourceStream.WriteAsync(bytes, cancellationToken);
39 | }
40 | }
41 |
42 | public async Task> LoadRenamingHistory(CancellationToken cancellationToken = default)
43 | {
44 | IEnumerable renamingSessionToEpisodes = await GetHistoryFromFile(cancellationToken);
45 |
46 | return await Task.FromResult(renamingSessionToEpisodes);
47 | }
48 |
49 | private static async Task> GetHistoryFromFile(CancellationToken cancellationToken)
50 | {
51 | List? renamingSessionToEpisodes = [];
52 | var filePath = FolderHelper.GetHistoryFileName();
53 |
54 | if (!File.Exists(filePath))
55 | {
56 | return renamingSessionToEpisodes;
57 | }
58 |
59 | var text = await File.ReadAllTextAsync(filePath, Encoding.UTF8, cancellationToken);
60 |
61 | await Task.Factory.StartNew(
62 | () =>
63 | {
64 | renamingSessionToEpisodes = JsonConvert.DeserializeObject>(text);
65 | }, cancellationToken);
66 |
67 | return renamingSessionToEpisodes;
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/src/BulkRename/Models/PreparationDatabase.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.Models
2 | {
3 | using System.Diagnostics;
4 | using BulkRename.Models.Entities;
5 | using Microsoft.Data.SqlClient;
6 | using Microsoft.EntityFrameworkCore;
7 |
8 | public class PreparationDatabase
9 | {
10 | private readonly ILogger _logger;
11 |
12 | private readonly Stopwatch _stopwatch;
13 |
14 | private int _connectionTimeOut;
15 |
16 | public PreparationDatabase(ILogger logger)
17 | {
18 | _logger = logger;
19 | _stopwatch = new Stopwatch();
20 | }
21 |
22 | public async Task PreparatePopulation(IApplicationBuilder applicationBuilder, int connectionTimeOut)
23 | {
24 | _connectionTimeOut = connectionTimeOut;
25 | _stopwatch.Start();
26 |
27 | using (var serviceScope = applicationBuilder.ApplicationServices.GetService()!.CreateScope())
28 | {
29 | var context = serviceScope.ServiceProvider.GetService();
30 |
31 | await TrySeedData(context!);
32 | }
33 | }
34 |
35 | private async Task TrySeedData(DbContext context)
36 | {
37 | try
38 | {
39 | _logger.LogInformation("Trying to apply migrations...");
40 | var appliedMigrations = await context!.Database.GetAppliedMigrationsAsync();
41 | LogMigrationInformation("applied", appliedMigrations);
42 | var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
43 | LogMigrationInformation("pending", pendingMigrations);
44 | await context.Database.MigrateAsync();
45 | await context.DisposeAsync();
46 | _logger.LogInformation("Migration finished");
47 | }
48 | catch (SqlException exception)
49 | {
50 | if (_stopwatch.ElapsedMilliseconds > _connectionTimeOut)
51 | {
52 | _logger.LogError($"Connection timeout of {_connectionTimeOut}ms ellapsed! Application is shutting down...", exception);
53 | throw;
54 | }
55 |
56 | _logger.LogWarning("Database is propably not ready...");
57 | _logger.LogWarning($"Connection failed, will try again. Max timeout = {_connectionTimeOut}ms");
58 |
59 | await Task.Delay(1000);
60 | await TrySeedData(context);
61 | }
62 | }
63 |
64 | private void LogMigrationInformation(string state, IEnumerable migrations)
65 | {
66 | if (migrations.Any())
67 | {
68 | _logger.LogInformation($"Following migrations are {state}");
69 | foreach (var migration in migrations)
70 | {
71 | _logger.LogInformation($"{migration}", migration);
72 | }
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/src/BulkRename/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @using BulkRename.Constants
2 | @using Microsoft.Extensions.Localization
3 |
4 | @inject IStringLocalizer SharedLocalizer
5 |
6 |
7 |
8 |
9 | @await Html.PartialAsync("_Favicons")
10 |
11 |
12 | BulkRename - @ViewData["Title"]
13 |
14 |
15 |
16 |
17 |
18 |
42 |
43 |
44 | @RenderBody()
45 |
46 |
47 |
48 |
55 |
56 |
57 |
58 | @await RenderSectionAsync("Scripts", required: false)
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125315_CreateTableEpisodeItems.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using BulkRename.Models.Entities;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace BulkRename.Migrations
11 | {
12 | [DbContext(typeof(BulkRenameContext))]
13 | [Migration("20200726125315_CreateTableEpisodeItems")]
14 | partial class CreateTableEpisodeItems
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.1.6")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
25 | {
26 | b.Property("EpisodeID")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("EpsNumberString")
31 | .HasColumnType("nvarchar(max)");
32 |
33 | b.Property("EpsSeasonID_FK")
34 | .HasColumnType("uniqueidentifier");
35 |
36 | b.HasKey("EpisodeID");
37 |
38 | b.HasIndex("EpsSeasonID_FK");
39 |
40 | b.ToTable("EpisodeItems");
41 | });
42 |
43 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
44 | {
45 | b.Property("SeasonID")
46 | .ValueGeneratedOnAdd()
47 | .HasColumnType("uniqueidentifier");
48 |
49 | b.Property("SsnNumberString")
50 | .HasColumnType("nvarchar(max)");
51 |
52 | b.Property("SsnSerieID_FK")
53 | .HasColumnType("uniqueidentifier");
54 |
55 | b.HasKey("SeasonID");
56 |
57 | b.HasIndex("SsnSerieID_FK");
58 |
59 | b.ToTable("SeasonItems");
60 | });
61 |
62 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Serie", b =>
63 | {
64 | b.Property("SerieID")
65 | .ValueGeneratedOnAdd()
66 | .HasColumnType("uniqueidentifier");
67 |
68 | b.Property("SerName")
69 | .HasColumnType("nvarchar(max)");
70 |
71 | b.HasKey("SerieID");
72 |
73 | b.ToTable("SerieItems");
74 | });
75 |
76 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
77 | {
78 | b.HasOne("BulkRename.Shared.Models.Entities.Season", "Season")
79 | .WithMany()
80 | .HasForeignKey("EpsSeasonID_FK")
81 | .OnDelete(DeleteBehavior.Cascade)
82 | .IsRequired();
83 | });
84 |
85 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
86 | {
87 | b.HasOne("BulkRename.Shared.Models.Entities.Serie", "Serie")
88 | .WithMany()
89 | .HasForeignKey("SsnSerieID_FK")
90 | .OnDelete(DeleteBehavior.Cascade)
91 | .IsRequired();
92 | });
93 | #pragma warning restore 612, 618
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/CreateEnvFile.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Generate .env file for docker-compose.yml
4 | .DESCRIPTION
5 | This script Generates a .env file that will be used in the docker-compose file. It can also used by a build server to populate data for automatic testing.
6 | .PARAMETER Version
7 | The version of the docker image, default is 'latest'
8 | .PARAMETER PersistanceMode
9 | Mode for persistance of the history, allowed values are 'None', 'Database', and 'Json'. Default is 'None'
10 | .PARAMETER Registry
11 | The docker registry for the image, default is 'docker.io/ramoy/'
12 | .PARAMETER DbName
13 | Name of the database that will be created, default is 'BulkRename_Web_DB_Live'
14 | .PARAMETER DbPort
15 | The external port of the SQL Server, default is '14333'
16 | .PARAMETER DbUser
17 | The database user, default is 'sa', it's not recommended to use sa in production'
18 | .PARAMETER SqlServerSaPassword
19 | Password for the SA user of the SQL Server, it doesn't have any default value
20 | .PARAMETER SeqUrl
21 | URL and port of your Seq installation, leave it empty if you don't have any
22 | .PARAMETER SeqApiKey
23 | The API Key of your Seq installation, leave it empty if you don't have any
24 | .PARAMETER BulkRenamePort
25 | Port of the bulkrename application, default is '8383'
26 | .PARAMETER BulkRenameFolder
27 | Folder outside docker that will be mapped to use for the files to rename, default is 'D:\\BulkRename\\Files'
28 | .PARAMETER LogFolder
29 | Folder where logs will be stored if seq is not configured, default is 'D:\\BulkRename\\Logs'
30 | .PARAMETER HistoryFolder
31 | The history file folder if you decide to use the file history instead of database, default is 'D:\\BulkRename\\RenamingHistory'
32 | .PARAMETER SupportedFileEndings
33 | File endings that are used for videos or the files you want to rename, default is 'mkv;mp4;m4v;avi'
34 | .PARAMETER FoldersToIgnore
35 | Folders which should be ignored, default is '.@__thumb'
36 | #>
37 |
38 | [CmdletBinding()]
39 | Param (
40 | [Parameter(Mandatory = $false)][string]$Version = "latest",
41 | [Parameter(Mandatory = $false)][string]$Registry = "docker.io/ramoy/",
42 | [Parameter(Mandatory = $false)][string]$PersistanceMode = "None",
43 | [Parameter(Mandatory = $false)][string]$DbName = "BulkRename_Web_DB_Live",
44 | [Parameter(Mandatory = $false)][string]$DbPort = "14333",
45 | [Parameter(Mandatory = $false)][string]$DbUser = "sa",
46 | [Parameter(Mandatory = $false)][string]$SqlServerSaPassword,
47 | [Parameter(Mandatory = $false)][string]$SeqUrl,
48 | [Parameter(Mandatory = $false)][string]$SeqApiKey,
49 | [Parameter(Mandatory = $false)][string]$BulkRenamePort = "8383",
50 | [Parameter(Mandatory = $false)][string]$BulkRenameFolder = "D:\\BulkRename\\Files",
51 | [Parameter(Mandatory = $false)][string]$LogFolder = "D:\\BulkRename\\Logs",
52 | [Parameter(Mandatory = $false)][string]$HistoryFolder = "D:\\BulkRename\\RenamingHistory",
53 | [Parameter(Mandatory = $false)][string]$SupportedFileEndings = "mkv;mp4;m4v;avi",
54 | [Parameter(Mandatory = $false)][string]$FoldersToIgnore = ".@__thumb"
55 | )
56 |
57 | # Creates file .env
58 | New-Item -ItemType File -Name ".env"
59 |
60 | # Fills sample values for the created files
61 | Set-Content .\.env "VERSION=$Version"
62 | Add-Content .\.env "PERSITANCE_MODE=$PersistanceMode"
63 | Add-Content .\.env "DOCKER_REGISTRY=$Registry"
64 | Add-Content .\.env "DB_NAME=$DbName"
65 | Add-Content .\.env "SQL_SERVER_EXTERNAL_PORT=$DbPort"
66 | Add-Content .\.env "DB_USER=$DbUser"
67 | Add-Content .\.env "SQL_SERVER_SA_PASSWORD=$SqlServerSaPassword"
68 | Add-Content .\.env "SEQ_URL=$SeqUrl"
69 | Add-Content .\.env "SEQ_API_KEY=$SeqApiKey"
70 | Add-Content .\.env "BULK_RENAME_PORT=$BulkRenamePort"
71 | Add-Content .\.env "BULK_RENAME_FOLDER=$BulkRenameFolder"
72 | Add-Content .\.env "LOG_FOLDER=$LogFolder"
73 | Add-Content .\.env "HISTORY_FOLDER=$HistoryFolder"
74 | Add-Content .\.env "SUPPORTED_FILE_ENDINGS=$SupportedFileEndings"
75 | Add-Content .\.env "FOLDERS_TO_IGNORE=$FoldersToIgnore"
76 |
77 | Write-Host "File has been created, listing all files in directory:"
78 | $Files = Get-ChildItem -Force
79 | foreach ($File in $Files) {
80 | Write-Host $File
81 | }
82 |
--------------------------------------------------------------------------------
/src/BulkRename/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/src/BulkRename/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125357_CreateTableRenamingSessionItems.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using BulkRename.Models.Entities;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace BulkRename.Migrations
11 | {
12 | [DbContext(typeof(BulkRenameContext))]
13 | [Migration("20200726125357_CreateTableRenamingSessionItems")]
14 | partial class CreateTableRenamingSessionItems
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.1.6")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
25 | {
26 | b.Property("EpisodeID")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("EpsNumberString")
31 | .HasColumnType("nvarchar(max)");
32 |
33 | b.Property("EpsSeasonID_FK")
34 | .HasColumnType("uniqueidentifier");
35 |
36 | b.HasKey("EpisodeID");
37 |
38 | b.HasIndex("EpsSeasonID_FK");
39 |
40 | b.ToTable("EpisodeItems");
41 | });
42 |
43 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSession", b =>
44 | {
45 | b.Property("RenamingSessionID")
46 | .ValueGeneratedOnAdd()
47 | .HasColumnType("uniqueidentifier");
48 |
49 | b.Property("RenExecutingDateTime")
50 | .HasColumnType("datetime2");
51 |
52 | b.Property("RenName")
53 | .HasColumnType("nvarchar(max)");
54 |
55 | b.HasKey("RenamingSessionID");
56 |
57 | b.ToTable("RenamingSessionItems");
58 | });
59 |
60 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
61 | {
62 | b.Property("SeasonID")
63 | .ValueGeneratedOnAdd()
64 | .HasColumnType("uniqueidentifier");
65 |
66 | b.Property("SsnNumberString")
67 | .HasColumnType("nvarchar(max)");
68 |
69 | b.Property("SsnSerieID_FK")
70 | .HasColumnType("uniqueidentifier");
71 |
72 | b.HasKey("SeasonID");
73 |
74 | b.HasIndex("SsnSerieID_FK");
75 |
76 | b.ToTable("SeasonItems");
77 | });
78 |
79 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Serie", b =>
80 | {
81 | b.Property("SerieID")
82 | .ValueGeneratedOnAdd()
83 | .HasColumnType("uniqueidentifier");
84 |
85 | b.Property("SerName")
86 | .HasColumnType("nvarchar(max)");
87 |
88 | b.HasKey("SerieID");
89 |
90 | b.ToTable("SerieItems");
91 | });
92 |
93 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
94 | {
95 | b.HasOne("BulkRename.Shared.Models.Entities.Season", "Season")
96 | .WithMany()
97 | .HasForeignKey("EpsSeasonID_FK")
98 | .OnDelete(DeleteBehavior.Cascade)
99 | .IsRequired();
100 | });
101 |
102 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
103 | {
104 | b.HasOne("BulkRename.Shared.Models.Entities.Serie", "Serie")
105 | .WithMany()
106 | .HasForeignKey("SsnSerieID_FK")
107 | .OnDelete(DeleteBehavior.Cascade)
108 | .IsRequired();
109 | });
110 | #pragma warning restore 612, 618
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/BulkRename/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js:
--------------------------------------------------------------------------------
1 | // Unobtrusive validation support library for jQuery and jQuery Validate
2 | // Copyright (c) .NET Foundation. All rights reserved.
3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
4 | // @version v3.2.11
5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),m.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive});
6 |
--------------------------------------------------------------------------------
/src/BulkRename/Startup.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename
2 | {
3 | using System.Diagnostics;
4 |
5 | using BulkRename.Constants;
6 | using BulkRename.Extensions;
7 | using BulkRename.Interfaces;
8 | using BulkRename.Models;
9 | using BulkRename.Models.Entities;
10 | using BulkRename.Services;
11 |
12 | using Microsoft.EntityFrameworkCore;
13 |
14 | using Serilog;
15 |
16 | public class Startup
17 | {
18 | private string? _connectionString;
19 |
20 | public Startup(IConfiguration configuration)
21 | {
22 | Configuration = configuration;
23 | }
24 |
25 | public IConfiguration Configuration { get; }
26 |
27 | public void ConfigureServices(IServiceCollection services)
28 | {
29 | SetConnectionString();
30 |
31 | services.AddControllersWithViews();
32 |
33 | services.AddDbContext(opt => { opt.UseSqlServer(_connectionString, builder => builder.EnableRetryOnFailure(120, TimeSpan.FromSeconds(10), null)); });
34 | services.AddControllers();
35 |
36 | // INFO: Register Services
37 | services.AddTransient();
38 | services.AddTransient();
39 | ConfigurePersistance(services);
40 | }
41 |
42 | private void ConfigurePersistance(IServiceCollection services)
43 | {
44 | var persistanceMode = Configuration.GetValue(nameof(PersistanceMode));
45 | switch (persistanceMode)
46 | {
47 | case PersistanceMode.None:
48 | services.AddTransient();
49 | break;
50 | case PersistanceMode.Database:
51 | services.AddTransient();
52 | break;
53 | case PersistanceMode.Json:
54 | services.AddTransient();
55 | break;
56 | default:
57 | services.AddTransient();
58 | break;
59 | }
60 | }
61 |
62 | public async Task Configure(WebApplication app)
63 | {
64 | ConfigureLogger();
65 | await PreparateDatabase(app);
66 | }
67 |
68 | private void ConfigureLogger()
69 | {
70 | var seqUrl = Configuration[ConfigurationNameConstants.SEQ_URL];
71 |
72 | if (!string.IsNullOrEmpty(seqUrl))
73 | {
74 | var seqApiKey = Configuration[ConfigurationNameConstants.SEQ_API_KEY];
75 | Log.Logger = new LoggerConfiguration()
76 | .WriteTo.Seq(seqUrl, apiKey: seqApiKey)
77 | .WriteTo.Console().CreateLogger();
78 | }
79 | else
80 | {
81 | var currentDirectory = Directory.GetCurrentDirectory();
82 | var logPath = Path.Combine(currentDirectory, EnvironmentConstants.LOG_FILE_FOLDER, EnvironmentConstants.LOG_FILE_NAME);
83 | Log.Logger = new LoggerConfiguration()
84 | .WriteTo.File(path: logPath, rollingInterval: RollingInterval.Day)
85 | .WriteTo.Console().CreateLogger();
86 | }
87 | }
88 |
89 | private async Task PreparateDatabase(WebApplication app)
90 | {
91 | var persistanceMode = Configuration.GetValue(nameof(PersistanceMode));
92 | if (persistanceMode == PersistanceMode.Database)
93 | {
94 | var connectionTimeOut = GetConnectionTimeOut();
95 | var preparationDatabase = app.Services.GetService();
96 | var task = preparationDatabase!.PreparatePopulation(app, connectionTimeOut);
97 | await task;
98 | }
99 | }
100 |
101 | private void SetConnectionString()
102 | {
103 | SetConnectionStringFromConfig();
104 | SetDebugConnectionString();
105 | }
106 |
107 | [Conditional("DEBUG")]
108 | private void SetDebugConnectionString()
109 | {
110 | _connectionString = DefaultValueConstants.DEBUG_CONNECTION_STRING;
111 | }
112 |
113 | [Conditional("RELEASE")]
114 | private void SetConnectionStringFromConfig()
115 | {
116 | var databaseName = Configuration[ConfigurationNameConstants.DB_NAME];
117 | var password = Configuration[ConfigurationNameConstants.DB_PASSWORD];
118 | var port = Configuration[ConfigurationNameConstants.DB_PORT];
119 | var server = Configuration[ConfigurationNameConstants.DB_SERVER];
120 | var user = Configuration[ConfigurationNameConstants.DB_USER];
121 |
122 | // INFO: https://github.com/dotnet/SqlClient/issues/1479
123 | _connectionString = $"Server={server},{port};Database={databaseName};Trusted_Connection=False;User Id={user};Password={password};TrustServerCertificate=True";
124 | }
125 |
126 | private int GetConnectionTimeOut()
127 | {
128 | var connectionTimeOutInSecondsStringValue = Configuration[ConfigurationNameConstants.DB_CONNECTION_TIME_OUT_IN_SECONDS];
129 | int timeOutInSeconds = int.TryParse(connectionTimeOutInSecondsStringValue, out timeOutInSeconds) ? timeOutInSeconds : DefaultValueConstants.DEFAULT_DB_MIGRATION_CONNECTION_TIME_OUT_IN_SECONDS;
130 | var connectionTimeOut = timeOutInSeconds.ToMilliseconds();
131 |
132 | return connectionTimeOut;
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726125452_CreateTableRenamingSessionToEpisodeItems.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using BulkRename.Models.Entities;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace BulkRename.Migrations
11 | {
12 | [DbContext(typeof(BulkRenameContext))]
13 | [Migration("20200726125452_CreateTableRenamingSessionToEpisodeItems")]
14 | partial class CreateTableRenamingSessionToEpisodeItems
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.1.6")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
25 | {
26 | b.Property("EpisodeID")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("EpsNumberString")
31 | .HasColumnType("nvarchar(max)");
32 |
33 | b.Property("EpsSeasonID_FK")
34 | .HasColumnType("uniqueidentifier");
35 |
36 | b.HasKey("EpisodeID");
37 |
38 | b.HasIndex("EpsSeasonID_FK");
39 |
40 | b.ToTable("EpisodeItems");
41 | });
42 |
43 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSession", b =>
44 | {
45 | b.Property("RenamingSessionID")
46 | .ValueGeneratedOnAdd()
47 | .HasColumnType("uniqueidentifier");
48 |
49 | b.Property("RenExecutingDateTime")
50 | .HasColumnType("datetime2");
51 |
52 | b.Property("RenName")
53 | .HasColumnType("nvarchar(max)");
54 |
55 | b.HasKey("RenamingSessionID");
56 |
57 | b.ToTable("RenamingSessionItems");
58 | });
59 |
60 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSessionToEpisode", b =>
61 | {
62 | b.Property("RenamingSessionToEpisodeID")
63 | .ValueGeneratedOnAdd()
64 | .HasColumnType("uniqueidentifier");
65 |
66 | b.Property("RseEpisodeID_FK")
67 | .HasColumnType("uniqueidentifier");
68 |
69 | b.Property("RseRenamingSessionID_FK")
70 | .HasColumnType("uniqueidentifier");
71 |
72 | b.HasKey("RenamingSessionToEpisodeID");
73 |
74 | b.HasIndex("RseEpisodeID_FK");
75 |
76 | b.HasIndex("RseRenamingSessionID_FK");
77 |
78 | b.ToTable("RenamingSessionToEpisodeItems");
79 | });
80 |
81 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
82 | {
83 | b.Property("SeasonID")
84 | .ValueGeneratedOnAdd()
85 | .HasColumnType("uniqueidentifier");
86 |
87 | b.Property("SsnNumberString")
88 | .HasColumnType("nvarchar(max)");
89 |
90 | b.Property("SsnSerieID_FK")
91 | .HasColumnType("uniqueidentifier");
92 |
93 | b.HasKey("SeasonID");
94 |
95 | b.HasIndex("SsnSerieID_FK");
96 |
97 | b.ToTable("SeasonItems");
98 | });
99 |
100 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Serie", b =>
101 | {
102 | b.Property("SerieID")
103 | .ValueGeneratedOnAdd()
104 | .HasColumnType("uniqueidentifier");
105 |
106 | b.Property("SerName")
107 | .HasColumnType("nvarchar(max)");
108 |
109 | b.HasKey("SerieID");
110 |
111 | b.ToTable("SerieItems");
112 | });
113 |
114 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
115 | {
116 | b.HasOne("BulkRename.Shared.Models.Entities.Season", "Season")
117 | .WithMany()
118 | .HasForeignKey("EpsSeasonID_FK")
119 | .OnDelete(DeleteBehavior.Cascade)
120 | .IsRequired();
121 | });
122 |
123 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSessionToEpisode", b =>
124 | {
125 | b.HasOne("BulkRename.Shared.Models.Entities.Episode", "Episode")
126 | .WithMany()
127 | .HasForeignKey("RseEpisodeID_FK")
128 | .OnDelete(DeleteBehavior.Cascade)
129 | .IsRequired();
130 |
131 | b.HasOne("BulkRename.Shared.Models.Entities.RenamingSession", "RenamingSession")
132 | .WithMany()
133 | .HasForeignKey("RseRenamingSessionID_FK")
134 | .OnDelete(DeleteBehavior.Cascade)
135 | .IsRequired();
136 | });
137 |
138 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
139 | {
140 | b.HasOne("BulkRename.Shared.Models.Entities.Serie", "Serie")
141 | .WithMany()
142 | .HasForeignKey("SsnSerieID_FK")
143 | .OnDelete(DeleteBehavior.Cascade)
144 | .IsRequired();
145 | });
146 | #pragma warning restore 612, 618
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/BulkRenameContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using BulkRename.Models.Entities;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace BulkRename.Migrations
10 | {
11 | [DbContext(typeof(BulkRenameContext))]
12 | partial class BulkRenameContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasAnnotation("ProductVersion", "3.1.8")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
21 |
22 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
23 | {
24 | b.Property("EpisodeID")
25 | .ValueGeneratedOnAdd()
26 | .HasColumnType("uniqueidentifier");
27 |
28 | b.Property("EpsEpisodeNewName")
29 | .HasColumnType("nvarchar(max)");
30 |
31 | b.Property("EpsEpisodeOriginalName")
32 | .HasColumnType("nvarchar(max)");
33 |
34 | b.Property("EpsNumberString")
35 | .HasColumnType("nvarchar(max)");
36 |
37 | b.Property("EpsSeasonID_FK")
38 | .HasColumnType("uniqueidentifier");
39 |
40 | b.HasKey("EpisodeID");
41 |
42 | b.HasIndex("EpsSeasonID_FK");
43 |
44 | b.ToTable("EpisodeItems");
45 | });
46 |
47 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSession", b =>
48 | {
49 | b.Property("RenamingSessionID")
50 | .ValueGeneratedOnAdd()
51 | .HasColumnType("uniqueidentifier");
52 |
53 | b.Property("RenExecutingDateTime")
54 | .HasColumnType("datetime2");
55 |
56 | b.Property("RenName")
57 | .HasColumnType("nvarchar(max)");
58 |
59 | b.Property("RenRenamingSessionIsOk")
60 | .HasColumnType("bit");
61 |
62 | b.HasKey("RenamingSessionID");
63 |
64 | b.ToTable("RenamingSessionItems");
65 | });
66 |
67 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSessionToEpisode", b =>
68 | {
69 | b.Property("RenamingSessionToEpisodeID")
70 | .ValueGeneratedOnAdd()
71 | .HasColumnType("uniqueidentifier");
72 |
73 | b.Property("RseEpisodeID_FK")
74 | .HasColumnType("uniqueidentifier");
75 |
76 | b.Property("RseRenamingSessionID_FK")
77 | .HasColumnType("uniqueidentifier");
78 |
79 | b.HasKey("RenamingSessionToEpisodeID");
80 |
81 | b.HasIndex("RseEpisodeID_FK");
82 |
83 | b.HasIndex("RseRenamingSessionID_FK");
84 |
85 | b.ToTable("RenamingSessionToEpisodeItems");
86 | });
87 |
88 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
89 | {
90 | b.Property("SeasonID")
91 | .ValueGeneratedOnAdd()
92 | .HasColumnType("uniqueidentifier");
93 |
94 | b.Property("SsnNumberString")
95 | .HasColumnType("nvarchar(max)");
96 |
97 | b.Property("SsnSerieID_FK")
98 | .HasColumnType("uniqueidentifier");
99 |
100 | b.HasKey("SeasonID");
101 |
102 | b.HasIndex("SsnSerieID_FK");
103 |
104 | b.ToTable("SeasonItems");
105 | });
106 |
107 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Serie", b =>
108 | {
109 | b.Property("SerieID")
110 | .ValueGeneratedOnAdd()
111 | .HasColumnType("uniqueidentifier");
112 |
113 | b.Property("SerName")
114 | .HasColumnType("nvarchar(max)");
115 |
116 | b.HasKey("SerieID");
117 |
118 | b.ToTable("SerieItems");
119 | });
120 |
121 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
122 | {
123 | b.HasOne("BulkRename.Shared.Models.Entities.Season", "Season")
124 | .WithMany()
125 | .HasForeignKey("EpsSeasonID_FK")
126 | .OnDelete(DeleteBehavior.Cascade)
127 | .IsRequired();
128 | });
129 |
130 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSessionToEpisode", b =>
131 | {
132 | b.HasOne("BulkRename.Shared.Models.Entities.Episode", "Episode")
133 | .WithMany()
134 | .HasForeignKey("RseEpisodeID_FK")
135 | .OnDelete(DeleteBehavior.Cascade)
136 | .IsRequired();
137 |
138 | b.HasOne("BulkRename.Shared.Models.Entities.RenamingSession", "RenamingSession")
139 | .WithMany()
140 | .HasForeignKey("RseRenamingSessionID_FK")
141 | .OnDelete(DeleteBehavior.Cascade)
142 | .IsRequired();
143 | });
144 |
145 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
146 | {
147 | b.HasOne("BulkRename.Shared.Models.Entities.Serie", "Serie")
148 | .WithMany()
149 | .HasForeignKey("SsnSerieID_FK")
150 | .OnDelete(DeleteBehavior.Cascade)
151 | .IsRequired();
152 | });
153 | #pragma warning restore 612, 618
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20200726134350_AddColumnsEpsEpisodeOriginalNameAndEpsEpisodeNewName.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using BulkRename.Models.Entities;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace BulkRename.Migrations
11 | {
12 | [DbContext(typeof(BulkRenameContext))]
13 | [Migration("20200726134350_AddColumnsEpsEpisodeOriginalNameAndEpsEpisodeNewName")]
14 | partial class AddColumnsEpsEpisodeOriginalNameAndEpsEpisodeNewName
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.1.6")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
25 | {
26 | b.Property("EpisodeID")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("EpsEpisodeNewName")
31 | .HasColumnType("nvarchar(max)");
32 |
33 | b.Property("EpsEpisodeOriginalName")
34 | .HasColumnType("nvarchar(max)");
35 |
36 | b.Property("EpsNumberString")
37 | .HasColumnType("nvarchar(max)");
38 |
39 | b.Property("EpsSeasonID_FK")
40 | .HasColumnType("uniqueidentifier");
41 |
42 | b.HasKey("EpisodeID");
43 |
44 | b.HasIndex("EpsSeasonID_FK");
45 |
46 | b.ToTable("EpisodeItems");
47 | });
48 |
49 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSession", b =>
50 | {
51 | b.Property("RenamingSessionID")
52 | .ValueGeneratedOnAdd()
53 | .HasColumnType("uniqueidentifier");
54 |
55 | b.Property("RenExecutingDateTime")
56 | .HasColumnType("datetime2");
57 |
58 | b.Property("RenName")
59 | .HasColumnType("nvarchar(max)");
60 |
61 | b.HasKey("RenamingSessionID");
62 |
63 | b.ToTable("RenamingSessionItems");
64 | });
65 |
66 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSessionToEpisode", b =>
67 | {
68 | b.Property("RenamingSessionToEpisodeID")
69 | .ValueGeneratedOnAdd()
70 | .HasColumnType("uniqueidentifier");
71 |
72 | b.Property("RseEpisodeID_FK")
73 | .HasColumnType("uniqueidentifier");
74 |
75 | b.Property("RseRenamingSessionID_FK")
76 | .HasColumnType("uniqueidentifier");
77 |
78 | b.HasKey("RenamingSessionToEpisodeID");
79 |
80 | b.HasIndex("RseEpisodeID_FK");
81 |
82 | b.HasIndex("RseRenamingSessionID_FK");
83 |
84 | b.ToTable("RenamingSessionToEpisodeItems");
85 | });
86 |
87 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
88 | {
89 | b.Property("SeasonID")
90 | .ValueGeneratedOnAdd()
91 | .HasColumnType("uniqueidentifier");
92 |
93 | b.Property("SsnNumberString")
94 | .HasColumnType("nvarchar(max)");
95 |
96 | b.Property("SsnSerieID_FK")
97 | .HasColumnType("uniqueidentifier");
98 |
99 | b.HasKey("SeasonID");
100 |
101 | b.HasIndex("SsnSerieID_FK");
102 |
103 | b.ToTable("SeasonItems");
104 | });
105 |
106 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Serie", b =>
107 | {
108 | b.Property("SerieID")
109 | .ValueGeneratedOnAdd()
110 | .HasColumnType("uniqueidentifier");
111 |
112 | b.Property("SerName")
113 | .HasColumnType("nvarchar(max)");
114 |
115 | b.HasKey("SerieID");
116 |
117 | b.ToTable("SerieItems");
118 | });
119 |
120 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
121 | {
122 | b.HasOne("BulkRename.Shared.Models.Entities.Season", "Season")
123 | .WithMany()
124 | .HasForeignKey("EpsSeasonID_FK")
125 | .OnDelete(DeleteBehavior.Cascade)
126 | .IsRequired();
127 | });
128 |
129 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSessionToEpisode", b =>
130 | {
131 | b.HasOne("BulkRename.Shared.Models.Entities.Episode", "Episode")
132 | .WithMany()
133 | .HasForeignKey("RseEpisodeID_FK")
134 | .OnDelete(DeleteBehavior.Cascade)
135 | .IsRequired();
136 |
137 | b.HasOne("BulkRename.Shared.Models.Entities.RenamingSession", "RenamingSession")
138 | .WithMany()
139 | .HasForeignKey("RseRenamingSessionID_FK")
140 | .OnDelete(DeleteBehavior.Cascade)
141 | .IsRequired();
142 | });
143 |
144 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
145 | {
146 | b.HasOne("BulkRename.Shared.Models.Entities.Serie", "Serie")
147 | .WithMany()
148 | .HasForeignKey("SsnSerieID_FK")
149 | .OnDelete(DeleteBehavior.Cascade)
150 | .IsRequired();
151 | });
152 | #pragma warning restore 612, 618
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/BulkRename/Migrations/20210710121459_AddColumnRenRenamingSessionIsOk.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using BulkRename.Models.Entities;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace BulkRename.Migrations
11 | {
12 | [DbContext(typeof(BulkRenameContext))]
13 | [Migration("20210710121459_AddColumnRenRenamingSessionIsOk")]
14 | partial class AddColumnRenRenamingSessionIsOk
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.1.8")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
25 | {
26 | b.Property("EpisodeID")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("EpsEpisodeNewName")
31 | .HasColumnType("nvarchar(max)");
32 |
33 | b.Property("EpsEpisodeOriginalName")
34 | .HasColumnType("nvarchar(max)");
35 |
36 | b.Property("EpsNumberString")
37 | .HasColumnType("nvarchar(max)");
38 |
39 | b.Property("EpsSeasonID_FK")
40 | .HasColumnType("uniqueidentifier");
41 |
42 | b.HasKey("EpisodeID");
43 |
44 | b.HasIndex("EpsSeasonID_FK");
45 |
46 | b.ToTable("EpisodeItems");
47 | });
48 |
49 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSession", b =>
50 | {
51 | b.Property("RenamingSessionID")
52 | .ValueGeneratedOnAdd()
53 | .HasColumnType("uniqueidentifier");
54 |
55 | b.Property("RenExecutingDateTime")
56 | .HasColumnType("datetime2");
57 |
58 | b.Property("RenName")
59 | .HasColumnType("nvarchar(max)");
60 |
61 | b.Property("RenRenamingSessionIsOk")
62 | .HasColumnType("bit");
63 |
64 | b.HasKey("RenamingSessionID");
65 |
66 | b.ToTable("RenamingSessionItems");
67 | });
68 |
69 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSessionToEpisode", b =>
70 | {
71 | b.Property("RenamingSessionToEpisodeID")
72 | .ValueGeneratedOnAdd()
73 | .HasColumnType("uniqueidentifier");
74 |
75 | b.Property("RseEpisodeID_FK")
76 | .HasColumnType("uniqueidentifier");
77 |
78 | b.Property("RseRenamingSessionID_FK")
79 | .HasColumnType("uniqueidentifier");
80 |
81 | b.HasKey("RenamingSessionToEpisodeID");
82 |
83 | b.HasIndex("RseEpisodeID_FK");
84 |
85 | b.HasIndex("RseRenamingSessionID_FK");
86 |
87 | b.ToTable("RenamingSessionToEpisodeItems");
88 | });
89 |
90 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
91 | {
92 | b.Property("SeasonID")
93 | .ValueGeneratedOnAdd()
94 | .HasColumnType("uniqueidentifier");
95 |
96 | b.Property("SsnNumberString")
97 | .HasColumnType("nvarchar(max)");
98 |
99 | b.Property("SsnSerieID_FK")
100 | .HasColumnType("uniqueidentifier");
101 |
102 | b.HasKey("SeasonID");
103 |
104 | b.HasIndex("SsnSerieID_FK");
105 |
106 | b.ToTable("SeasonItems");
107 | });
108 |
109 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Serie", b =>
110 | {
111 | b.Property("SerieID")
112 | .ValueGeneratedOnAdd()
113 | .HasColumnType("uniqueidentifier");
114 |
115 | b.Property("SerName")
116 | .HasColumnType("nvarchar(max)");
117 |
118 | b.HasKey("SerieID");
119 |
120 | b.ToTable("SerieItems");
121 | });
122 |
123 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Episode", b =>
124 | {
125 | b.HasOne("BulkRename.Shared.Models.Entities.Season", "Season")
126 | .WithMany()
127 | .HasForeignKey("EpsSeasonID_FK")
128 | .OnDelete(DeleteBehavior.Cascade)
129 | .IsRequired();
130 | });
131 |
132 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.RenamingSessionToEpisode", b =>
133 | {
134 | b.HasOne("BulkRename.Shared.Models.Entities.Episode", "Episode")
135 | .WithMany()
136 | .HasForeignKey("RseEpisodeID_FK")
137 | .OnDelete(DeleteBehavior.Cascade)
138 | .IsRequired();
139 |
140 | b.HasOne("BulkRename.Shared.Models.Entities.RenamingSession", "RenamingSession")
141 | .WithMany()
142 | .HasForeignKey("RseRenamingSessionID_FK")
143 | .OnDelete(DeleteBehavior.Cascade)
144 | .IsRequired();
145 | });
146 |
147 | modelBuilder.Entity("BulkRename.Shared.Models.Entities.Season", b =>
148 | {
149 | b.HasOne("BulkRename.Shared.Models.Entities.Serie", "Serie")
150 | .WithMany()
151 | .HasForeignKey("SsnSerieID_FK")
152 | .OnDelete(DeleteBehavior.Cascade)
153 | .IsRequired();
154 | });
155 | #pragma warning restore 612, 618
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/.github/Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/src/BulkRename.IntegrationTests/Resources/LanguageResourcesTests.cs:
--------------------------------------------------------------------------------
1 | namespace BulkRename.IntegrationTests.Resources
2 | {
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Xml;
6 |
7 | [TestFixture]
8 | internal class LanguageResourcesTests
9 | {
10 | private const string APP_RESOURCES = "SharedResource";
11 |
12 | private const string APP_RESOURCES_FILE_ENDING = "resx";
13 |
14 | private const string DEFAULT_LANGUAGE_CODE = "en";
15 |
16 | private const string DEFAULT_LANGUAGE_RESOURCE_FILENAME = "SharedResource.resx";
17 |
18 | private const int LANGUAGE_CODE_LENGTH = 2;
19 |
20 | private List GetLanguageResourceFiles()
21 | {
22 | var files = Directory.GetFiles(@"../../../../BulkRename/Resources/", "*.resx").ToList();
23 | return files;
24 | }
25 |
26 | private List ReadLanguageResourceEntries(string filePath)
27 | {
28 | var list = new List();
29 |
30 | var document = new XmlDocument();
31 | document.Load(filePath);
32 | var node = document.GetElementsByTagName("data");
33 | foreach (XmlElement element in node)
34 | {
35 | var name = element.GetAttribute("name");
36 | var value = element.ChildNodes[1]!.InnerText;
37 |
38 | list.Add(new LanguageResourceEntry(name, value));
39 | }
40 |
41 | return list;
42 | }
43 |
44 | private Dictionary> GetLanguageDictionary()
45 | {
46 | var languageDictionary = new Dictionary>();
47 | var files = GetLanguageResourceFiles();
48 |
49 | var defaultLanguageFile = files.Single(f => Path.GetFileName(f).Equals(DEFAULT_LANGUAGE_RESOURCE_FILENAME));
50 | var defaultLanguageResourceEntries = ReadLanguageResourceEntries(defaultLanguageFile);
51 | languageDictionary.Add(DEFAULT_LANGUAGE_CODE, defaultLanguageResourceEntries);
52 |
53 | foreach (var filePath in files)
54 | {
55 | var fileName = Path.GetFileName(filePath);
56 | if (fileName == DEFAULT_LANGUAGE_RESOURCE_FILENAME)
57 | {
58 | continue;
59 | }
60 |
61 | var parts = fileName.Split('.');
62 | var language = parts[1];
63 | var languageResourceEntries = ReadLanguageResourceEntries(filePath);
64 |
65 | languageDictionary.Add(language, languageResourceEntries);
66 | }
67 |
68 | return languageDictionary;
69 | }
70 |
71 | [Test]
72 | public void GetAllLanguageFiles_CheckNamingConvention_NamingIsCorrect()
73 | {
74 | var files = GetLanguageResourceFiles();
75 |
76 | var defaultFileErrorMessage = $"There should be a file called '{DEFAULT_LANGUAGE_RESOURCE_FILENAME}', it actually doesn't exist";
77 | Assert.That(files.Any(f => Path.GetFileName(f).Equals(DEFAULT_LANGUAGE_RESOURCE_FILENAME)), defaultFileErrorMessage);
78 |
79 | foreach (var file in files)
80 | {
81 | var fileName = Path.GetFileName(file);
82 | if (fileName == DEFAULT_LANGUAGE_RESOURCE_FILENAME)
83 | {
84 | continue;
85 | }
86 |
87 | var parts = fileName.Split('.');
88 | var appResourcesPart = parts[0];
89 | var appResourcesPartErrorMessage = $"Language resource file name has to start with '{APP_RESOURCES}', actual one is '{appResourcesPart}'";
90 | Assert.That(appResourcesPart, Is.EqualTo(APP_RESOURCES), appResourcesPartErrorMessage);
91 |
92 | var languageCodePart = parts[1];
93 | var languageCodeErrorMessage = $"Language code should be {LANGUAGE_CODE_LENGTH} characters string, actual one is named '{languageCodePart}'";
94 | Assert.That(languageCodePart, Has.Length.EqualTo(LANGUAGE_CODE_LENGTH), languageCodeErrorMessage);
95 |
96 | var fileEndingPart = parts[2];
97 | var fileEndingPartErrorMessage = $"The language resource file name has to end with '{APP_RESOURCES_FILE_ENDING}', actual is '{fileEndingPart}'";
98 | Assert.That(fileEndingPart, Is.EqualTo(APP_RESOURCES_FILE_ENDING), fileEndingPartErrorMessage);
99 | }
100 | }
101 |
102 | [Test]
103 | public void ReadAllLanguageResources_CheckKeysCount_AllKeysCountAreSame()
104 | {
105 | // arrange
106 | var languageDictionary = GetLanguageDictionary();
107 |
108 | // act
109 | languageDictionary.TryGetValue(DEFAULT_LANGUAGE_CODE, out var defaultLanguageList);
110 | var defaultCount = defaultLanguageList!.Count;
111 |
112 | foreach (var dictionary in languageDictionary)
113 | {
114 | if (dictionary.Key.Equals(DEFAULT_LANGUAGE_CODE))
115 | {
116 | continue;
117 | }
118 |
119 | var currentCount = dictionary.Value.Count;
120 | var dictionaryCountErrorMessage = $"The language '{dictionary.Key}' should have {defaultCount} entries but actually has {currentCount} entries";
121 |
122 | // assert
123 | Assert.That(currentCount, Is.EqualTo(defaultCount), dictionaryCountErrorMessage);
124 | }
125 | }
126 |
127 | [Test]
128 | public void ReadAllLanguageResources_CheckAllValues_NoneOfThemIsEmpty()
129 | {
130 | // arrange
131 | var languageDictionary = GetLanguageDictionary();
132 |
133 | foreach (var dictionary in languageDictionary)
134 | {
135 | foreach (var entry in dictionary.Value)
136 | {
137 | // act
138 | var entryHasEmptyValueErrorMessage = $"Entry with the name '{entry.Name}' has an empty value in '{dictionary.Key}' language";
139 |
140 | // assert
141 | Assert.That(entry.Value, Is.Not.Empty, entryHasEmptyValueErrorMessage);
142 | }
143 | }
144 | }
145 |
146 | [Test]
147 | public void ReadAllLanguageResources_CheckOtherLanguages_AllEntriesExistInDefaultLanguage()
148 | {
149 | // arrange
150 | var languageDictionary = GetLanguageDictionary();
151 |
152 | // act
153 | languageDictionary.TryGetValue(DEFAULT_LANGUAGE_CODE, out var defaultLanguageList);
154 |
155 | foreach (var dictionary in languageDictionary)
156 | {
157 | if (dictionary.Key.Equals(DEFAULT_LANGUAGE_CODE))
158 | {
159 | continue;
160 | }
161 |
162 | foreach (var entry in dictionary.Value)
163 | {
164 | var exists = defaultLanguageList!.Any(l => l.Name.Equals(entry.Name));
165 | var entryDoesntExistInDefaultLanguageMessage = $"Entry with the name '{entry.Name}' does not exist in default language but in language '{dictionary.Key}'";
166 |
167 | // assert
168 | Assert.That(exists, entryDoesntExistInDefaultLanguageMessage);
169 | }
170 | }
171 | }
172 | }
173 | }
--------------------------------------------------------------------------------
/src/BulkRename.IntegrationTests/BulkRename.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | true
6 |
7 |
8 |
9 |
10 | all
11 | runtime; build; native; contentfiles; analyzers; buildtransitive
12 |
13 |
14 |
15 |
16 | all
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Always
30 |
31 |
32 | Always
33 |
34 |
35 | Always
36 |
37 |
38 | Always
39 |
40 |
41 | Always
42 |
43 |
44 | Always
45 |
46 |
47 | Always
48 |
49 |
50 | Always
51 |
52 |
53 | Always
54 |
55 |
56 | Always
57 |
58 |
59 | Always
60 |
61 |
62 | Always
63 |
64 |
65 | Always
66 |
67 |
68 | Always
69 |
70 |
71 | Always
72 |
73 |
74 | Always
75 |
76 |
77 | Always
78 |
79 |
80 | Always
81 |
82 |
83 | Always
84 |
85 |
86 | Always
87 |
88 |
89 | Always
90 |
91 |
92 | Always
93 |
94 |
95 | Always
96 |
97 |
98 | Always
99 |
100 |
101 | Always
102 |
103 |
104 | Always
105 |
106 |
107 | Always
108 |
109 |
110 | Always
111 |
112 |
113 | Always
114 |
115 |
116 | Always
117 |
118 |
119 | Always
120 |
121 |
122 | Always
123 |
124 |
125 | Always
126 |
127 |
128 | Always
129 |
130 |
131 | Always
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/BulkRename/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */
8 | *,
9 | *::before,
10 | *::after {
11 | box-sizing: border-box;
12 | }
13 |
14 | @media (prefers-reduced-motion: no-preference) {
15 | :root {
16 | scroll-behavior: smooth;
17 | }
18 | }
19 |
20 | body {
21 | margin: 0;
22 | font-family: var(--bs-body-font-family);
23 | font-size: var(--bs-body-font-size);
24 | font-weight: var(--bs-body-font-weight);
25 | line-height: var(--bs-body-line-height);
26 | color: var(--bs-body-color);
27 | text-align: var(--bs-body-text-align);
28 | background-color: var(--bs-body-bg);
29 | -webkit-text-size-adjust: 100%;
30 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
31 | }
32 |
33 | hr {
34 | margin: 1rem 0;
35 | color: inherit;
36 | background-color: currentColor;
37 | border: 0;
38 | opacity: 0.25;
39 | }
40 |
41 | hr:not([size]) {
42 | height: 1px;
43 | }
44 |
45 | h6, h5, h4, h3, h2, h1 {
46 | margin-top: 0;
47 | margin-bottom: 0.5rem;
48 | font-weight: 500;
49 | line-height: 1.2;
50 | }
51 |
52 | h1 {
53 | font-size: calc(1.375rem + 1.5vw);
54 | }
55 | @media (min-width: 1200px) {
56 | h1 {
57 | font-size: 2.5rem;
58 | }
59 | }
60 |
61 | h2 {
62 | font-size: calc(1.325rem + 0.9vw);
63 | }
64 | @media (min-width: 1200px) {
65 | h2 {
66 | font-size: 2rem;
67 | }
68 | }
69 |
70 | h3 {
71 | font-size: calc(1.3rem + 0.6vw);
72 | }
73 | @media (min-width: 1200px) {
74 | h3 {
75 | font-size: 1.75rem;
76 | }
77 | }
78 |
79 | h4 {
80 | font-size: calc(1.275rem + 0.3vw);
81 | }
82 | @media (min-width: 1200px) {
83 | h4 {
84 | font-size: 1.5rem;
85 | }
86 | }
87 |
88 | h5 {
89 | font-size: 1.25rem;
90 | }
91 |
92 | h6 {
93 | font-size: 1rem;
94 | }
95 |
96 | p {
97 | margin-top: 0;
98 | margin-bottom: 1rem;
99 | }
100 |
101 | abbr[title],
102 | abbr[data-bs-original-title] {
103 | -webkit-text-decoration: underline dotted;
104 | text-decoration: underline dotted;
105 | cursor: help;
106 | -webkit-text-decoration-skip-ink: none;
107 | text-decoration-skip-ink: none;
108 | }
109 |
110 | address {
111 | margin-bottom: 1rem;
112 | font-style: normal;
113 | line-height: inherit;
114 | }
115 |
116 | ol,
117 | ul {
118 | padding-right: 2rem;
119 | }
120 |
121 | ol,
122 | ul,
123 | dl {
124 | margin-top: 0;
125 | margin-bottom: 1rem;
126 | }
127 |
128 | ol ol,
129 | ul ul,
130 | ol ul,
131 | ul ol {
132 | margin-bottom: 0;
133 | }
134 |
135 | dt {
136 | font-weight: 700;
137 | }
138 |
139 | dd {
140 | margin-bottom: 0.5rem;
141 | margin-right: 0;
142 | }
143 |
144 | blockquote {
145 | margin: 0 0 1rem;
146 | }
147 |
148 | b,
149 | strong {
150 | font-weight: bolder;
151 | }
152 |
153 | small {
154 | font-size: 0.875em;
155 | }
156 |
157 | mark {
158 | padding: 0.2em;
159 | background-color: #fcf8e3;
160 | }
161 |
162 | sub,
163 | sup {
164 | position: relative;
165 | font-size: 0.75em;
166 | line-height: 0;
167 | vertical-align: baseline;
168 | }
169 |
170 | sub {
171 | bottom: -0.25em;
172 | }
173 |
174 | sup {
175 | top: -0.5em;
176 | }
177 |
178 | a {
179 | color: #0d6efd;
180 | text-decoration: underline;
181 | }
182 | a:hover {
183 | color: #0a58ca;
184 | }
185 |
186 | a:not([href]):not([class]), a:not([href]):not([class]):hover {
187 | color: inherit;
188 | text-decoration: none;
189 | }
190 |
191 | pre,
192 | code,
193 | kbd,
194 | samp {
195 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
196 | font-size: 1em;
197 | direction: ltr ;
198 | unicode-bidi: bidi-override;
199 | }
200 |
201 | pre {
202 | display: block;
203 | margin-top: 0;
204 | margin-bottom: 1rem;
205 | overflow: auto;
206 | font-size: 0.875em;
207 | }
208 | pre code {
209 | font-size: inherit;
210 | color: inherit;
211 | word-break: normal;
212 | }
213 |
214 | code {
215 | font-size: 0.875em;
216 | color: #d63384;
217 | word-wrap: break-word;
218 | }
219 | a > code {
220 | color: inherit;
221 | }
222 |
223 | kbd {
224 | padding: 0.2rem 0.4rem;
225 | font-size: 0.875em;
226 | color: #fff;
227 | background-color: #212529;
228 | border-radius: 0.2rem;
229 | }
230 | kbd kbd {
231 | padding: 0;
232 | font-size: 1em;
233 | font-weight: 700;
234 | }
235 |
236 | figure {
237 | margin: 0 0 1rem;
238 | }
239 |
240 | img,
241 | svg {
242 | vertical-align: middle;
243 | }
244 |
245 | table {
246 | caption-side: bottom;
247 | border-collapse: collapse;
248 | }
249 |
250 | caption {
251 | padding-top: 0.5rem;
252 | padding-bottom: 0.5rem;
253 | color: #6c757d;
254 | text-align: right;
255 | }
256 |
257 | th {
258 | text-align: inherit;
259 | text-align: -webkit-match-parent;
260 | }
261 |
262 | thead,
263 | tbody,
264 | tfoot,
265 | tr,
266 | td,
267 | th {
268 | border-color: inherit;
269 | border-style: solid;
270 | border-width: 0;
271 | }
272 |
273 | label {
274 | display: inline-block;
275 | }
276 |
277 | button {
278 | border-radius: 0;
279 | }
280 |
281 | button:focus:not(:focus-visible) {
282 | outline: 0;
283 | }
284 |
285 | input,
286 | button,
287 | select,
288 | optgroup,
289 | textarea {
290 | margin: 0;
291 | font-family: inherit;
292 | font-size: inherit;
293 | line-height: inherit;
294 | }
295 |
296 | button,
297 | select {
298 | text-transform: none;
299 | }
300 |
301 | [role=button] {
302 | cursor: pointer;
303 | }
304 |
305 | select {
306 | word-wrap: normal;
307 | }
308 | select:disabled {
309 | opacity: 1;
310 | }
311 |
312 | [list]::-webkit-calendar-picker-indicator {
313 | display: none;
314 | }
315 |
316 | button,
317 | [type=button],
318 | [type=reset],
319 | [type=submit] {
320 | -webkit-appearance: button;
321 | }
322 | button:not(:disabled),
323 | [type=button]:not(:disabled),
324 | [type=reset]:not(:disabled),
325 | [type=submit]:not(:disabled) {
326 | cursor: pointer;
327 | }
328 |
329 | ::-moz-focus-inner {
330 | padding: 0;
331 | border-style: none;
332 | }
333 |
334 | textarea {
335 | resize: vertical;
336 | }
337 |
338 | fieldset {
339 | min-width: 0;
340 | padding: 0;
341 | margin: 0;
342 | border: 0;
343 | }
344 |
345 | legend {
346 | float: right;
347 | width: 100%;
348 | padding: 0;
349 | margin-bottom: 0.5rem;
350 | font-size: calc(1.275rem + 0.3vw);
351 | line-height: inherit;
352 | }
353 | @media (min-width: 1200px) {
354 | legend {
355 | font-size: 1.5rem;
356 | }
357 | }
358 | legend + * {
359 | clear: right;
360 | }
361 |
362 | ::-webkit-datetime-edit-fields-wrapper,
363 | ::-webkit-datetime-edit-text,
364 | ::-webkit-datetime-edit-minute,
365 | ::-webkit-datetime-edit-hour-field,
366 | ::-webkit-datetime-edit-day-field,
367 | ::-webkit-datetime-edit-month-field,
368 | ::-webkit-datetime-edit-year-field {
369 | padding: 0;
370 | }
371 |
372 | ::-webkit-inner-spin-button {
373 | height: auto;
374 | }
375 |
376 | [type=search] {
377 | outline-offset: -2px;
378 | -webkit-appearance: textfield;
379 | }
380 |
381 | [type="tel"],
382 | [type="url"],
383 | [type="email"],
384 | [type="number"] {
385 | direction: ltr;
386 | }
387 | ::-webkit-search-decoration {
388 | -webkit-appearance: none;
389 | }
390 |
391 | ::-webkit-color-swatch-wrapper {
392 | padding: 0;
393 | }
394 |
395 | ::file-selector-button {
396 | font: inherit;
397 | }
398 |
399 | ::-webkit-file-upload-button {
400 | font: inherit;
401 | -webkit-appearance: button;
402 | }
403 |
404 | output {
405 | display: inline-block;
406 | }
407 |
408 | iframe {
409 | border: 0;
410 | }
411 |
412 | summary {
413 | display: list-item;
414 | cursor: pointer;
415 | }
416 |
417 | progress {
418 | vertical-align: baseline;
419 | }
420 |
421 | [hidden] {
422 | display: none !important;
423 | }
424 | /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
--------------------------------------------------------------------------------
/DEVELOPMENT.md:
--------------------------------------------------------------------------------
1 | - [Description](#description)
2 | - [Documentation](#documentation)
3 | - [Branches](#branches)
4 | - [Workflows](#workflows)
5 | - [Variables and secrets](#variables-and-secrets)
6 | - [.env file](#env-file)
7 | - [Languages](#languages)
8 | - [Resource file](#resource-file)
9 | - [Integration Tests](#integration-tests)
10 | - [Add language to the supported cultures](#add-language-to-the-supported-cultures)
11 | - [Version](#version)
12 | - [Database fields](#database-fields)
13 | - [Primary key](#primary-key)
14 | - [All fields](#all-fields)
15 | - [Foreign Keys](#foreign-keys)
16 | - [Working locally](#working-locally)
17 | - [Local build](#local-build)
18 | - [Running the container](#running-the-container)
19 |
20 | # Description
21 | You are welcome to participate in the development of this tool, in this file some information and rules for the development are described.
22 |
23 | # Documentation
24 | If changes are made to the interface or functionality, the screenshots and documentation should also be updated.
25 |
26 | # Branches
27 | To contribute, please create a fork of the repository, work on a feature branch and put a pull request in the `develop` branch. Once ci has run successfully with the tests, it can be merged into the `develop` branch. The `release` branch is created from the `master` branch and [semantic versioning](https://semver.org) is used.
28 |
29 | # Workflows
30 | The workflows are described in the following table:
31 |
32 | | Workflow | File name | Trigger | Description |
33 | |----------------|----------------------------------------------------------------------------------------|------------------------------------------------------|----------------------------------------------------------------------------------------|
34 | | ci | [build-test-cleanup.yml](./.github/workflows/build-test-cleanup.yml) | All branches and pull requests to develop and master | Builds and tests the software |
35 | | release | [build-test-release-cleanup.yml](./.github/workflows/build-test-release-cleanup.yml) | Commits to develop, master and release/* branches | Builds and tests software and pushes it to the Docker Hub if the tests were successful |
36 | | manual_release | [build-test-release-cleanup_manually.yml](./.github/workflows/build-test-release-cleanup_manually.yml) | Manual, can be triggeret on any branch | Builds and tests software and pushes it to the Docker Hub if the tests were successful |
37 | | update-readme | [update-dockerhub-readme.yml](./.github/workflows/update-dockerhub-readme.yml) | Commits on master branch | Updates ReadMe on docker hub |
38 |
39 | Version names are automatically set by the reusable workflow `set_version`, based on branch names. See in the file [set_version.yml](./.github/workflows/set_version.yml)
40 |
41 | ## Variables and secrets
42 | In order for the workflows to run on your GitHub account, the following variables and secrets must be set:
43 |
44 | | Name | Type | Description |
45 | |------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
46 | | IMAGE_NAME | Variable | Name of the image, e.g. bulkrename |
47 | | SQL_SERVER_SA_PASSWORD | Secret | Password for SA user, refer also to [Password Policy](https://learn.microsoft.com/en-us/sql/relational-databases/security/password-policy?view=sql-server-ver16) |
48 | | DOCKERHUB_USERNAME | Secret | Used in the release workflow for docker login |
49 | | DOCKERHUB_TOKEN | Secret | Is used in the release workflow for the Docker login, must be created first, see [Docker Hub documentation](https://docs.docker.com/security/for-developers/access-tokens/) |
50 |
51 | # .env file
52 | In order that parameter values are not directly in the docker-compose.yml, the .env file is used here. This allows the parameters and values to be specified as a key-value pair in this file. The [CreateEnvFile.ps1](./src/CreateEnvFile.ps1) script was created to avoid having to create the file manually. If new parameters are defined for the docker-compose.yml file, the script should be extended accordingly.
53 |
54 | # Languages
55 | A new language can be added very easily, you need Visual Studio, you can download it [here](https://visualstudio.microsoft.com/downloads/). In this example, we will add `Spanish` as a new language.
56 |
57 | ## Resource file
58 | Create a new resource file in the folder [Resources](./src/BulkRename/Resources) and provide your language code between the file `SharedResource` name and the extension `resx`, for example `SharedResource.es.resx`. Copy all the keys from the [default language file](./src/BulkRename/Resources/SharedResource.resx) which is English, and add the translations. Check out this [Microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization/provide-resources?view=aspnetcore-8.0) to learn more about resource files.
59 |
60 | ### Integration Tests
61 | There are a few integration tests, that will ensure that all the language keys, that exist in the English version, have also been translated to the new language. Please run [these tests](./src/BulkRename.IntegrationTests/Resources/LanguageResourcesTests.cs) before creating a pull request.
62 |
63 | ## Add language to the supported cultures
64 | Go to the class [Program.cs](./src/BulkRename/Program.cs) and add your language to the `supportedCultures` with the corresponding culture.
65 |
66 | ```c#
67 | var supportedCultures = new[]
68 | {
69 | new CultureInfo(defaultCulture),
70 | new CultureInfo("hu"),
71 | new CultureInfo("de"),
72 | // Add here your new CultureInfo
73 | new CultureInfo("es")
74 | };
75 | ```
76 |
77 | # Version
78 | The version is set in the following files:
79 | - VERSION in [Dockerfile](./src/Dockerfile)
80 | - VersionPrefix in [Directory.Build.props](./src/Directory.Build.props)
81 | - Parameter `-Version` in [CreateEnvFile.ps1](./src/CreateEnvFile.ps1)
82 |
83 | # Database fields
84 | ### Primary key
85 | Primary keys of a table have a suffix `ID` after the table name. For example, the table `Episode` has the field `EpisodeID` as GUID.
86 |
87 | ### All fields
88 | All fields except the primary key fields follow a prefix with the shortened 3-letter name of the table. For the table `Episode` we can use `Eps` for the field `EpsEpisodeName`.
89 |
90 | ### Foreign Keys
91 | Foreign key fields follow the given pattern:
92 | - `Eps` as the general preffix
93 | - `SeasonID` same name as the primary key field of the referenced table
94 | - `_FK` as a suffix to highlight that it is a foreign key
95 |
96 | # Working locally
97 | ## Local build
98 | To build the docker image locally, you can run the command `docker build -t bulkrename:latest .` in the root folder of the solution.
99 |
100 | ## Running the container
101 | As long as you have an .env file in the root directory of the solution, run `docker-compose up -d` and the container will be started together with the database. To establish a database connection, a value of the environment variable `SqlServerSaPassword` must be defined manually and the `PersistanceMode` must be set to Database. Also check whether the mapped folder paths exist on your computer, by default everything is mapped to the `D:\` drive, but can be changed.
102 |
--------------------------------------------------------------------------------
/src/BulkRename/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */
8 | *,
9 | *::before,
10 | *::after {
11 | box-sizing: border-box;
12 | }
13 |
14 | @media (prefers-reduced-motion: no-preference) {
15 | :root {
16 | scroll-behavior: smooth;
17 | }
18 | }
19 |
20 | body {
21 | margin: 0;
22 | font-family: var(--bs-body-font-family);
23 | font-size: var(--bs-body-font-size);
24 | font-weight: var(--bs-body-font-weight);
25 | line-height: var(--bs-body-line-height);
26 | color: var(--bs-body-color);
27 | text-align: var(--bs-body-text-align);
28 | background-color: var(--bs-body-bg);
29 | -webkit-text-size-adjust: 100%;
30 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
31 | }
32 |
33 | hr {
34 | margin: 1rem 0;
35 | color: inherit;
36 | background-color: currentColor;
37 | border: 0;
38 | opacity: 0.25;
39 | }
40 |
41 | hr:not([size]) {
42 | height: 1px;
43 | }
44 |
45 | h6, h5, h4, h3, h2, h1 {
46 | margin-top: 0;
47 | margin-bottom: 0.5rem;
48 | font-weight: 500;
49 | line-height: 1.2;
50 | }
51 |
52 | h1 {
53 | font-size: calc(1.375rem + 1.5vw);
54 | }
55 | @media (min-width: 1200px) {
56 | h1 {
57 | font-size: 2.5rem;
58 | }
59 | }
60 |
61 | h2 {
62 | font-size: calc(1.325rem + 0.9vw);
63 | }
64 | @media (min-width: 1200px) {
65 | h2 {
66 | font-size: 2rem;
67 | }
68 | }
69 |
70 | h3 {
71 | font-size: calc(1.3rem + 0.6vw);
72 | }
73 | @media (min-width: 1200px) {
74 | h3 {
75 | font-size: 1.75rem;
76 | }
77 | }
78 |
79 | h4 {
80 | font-size: calc(1.275rem + 0.3vw);
81 | }
82 | @media (min-width: 1200px) {
83 | h4 {
84 | font-size: 1.5rem;
85 | }
86 | }
87 |
88 | h5 {
89 | font-size: 1.25rem;
90 | }
91 |
92 | h6 {
93 | font-size: 1rem;
94 | }
95 |
96 | p {
97 | margin-top: 0;
98 | margin-bottom: 1rem;
99 | }
100 |
101 | abbr[title],
102 | abbr[data-bs-original-title] {
103 | -webkit-text-decoration: underline dotted;
104 | text-decoration: underline dotted;
105 | cursor: help;
106 | -webkit-text-decoration-skip-ink: none;
107 | text-decoration-skip-ink: none;
108 | }
109 |
110 | address {
111 | margin-bottom: 1rem;
112 | font-style: normal;
113 | line-height: inherit;
114 | }
115 |
116 | ol,
117 | ul {
118 | padding-left: 2rem;
119 | }
120 |
121 | ol,
122 | ul,
123 | dl {
124 | margin-top: 0;
125 | margin-bottom: 1rem;
126 | }
127 |
128 | ol ol,
129 | ul ul,
130 | ol ul,
131 | ul ol {
132 | margin-bottom: 0;
133 | }
134 |
135 | dt {
136 | font-weight: 700;
137 | }
138 |
139 | dd {
140 | margin-bottom: 0.5rem;
141 | margin-left: 0;
142 | }
143 |
144 | blockquote {
145 | margin: 0 0 1rem;
146 | }
147 |
148 | b,
149 | strong {
150 | font-weight: bolder;
151 | }
152 |
153 | small {
154 | font-size: 0.875em;
155 | }
156 |
157 | mark {
158 | padding: 0.2em;
159 | background-color: #fcf8e3;
160 | }
161 |
162 | sub,
163 | sup {
164 | position: relative;
165 | font-size: 0.75em;
166 | line-height: 0;
167 | vertical-align: baseline;
168 | }
169 |
170 | sub {
171 | bottom: -0.25em;
172 | }
173 |
174 | sup {
175 | top: -0.5em;
176 | }
177 |
178 | a {
179 | color: #0d6efd;
180 | text-decoration: underline;
181 | }
182 | a:hover {
183 | color: #0a58ca;
184 | }
185 |
186 | a:not([href]):not([class]), a:not([href]):not([class]):hover {
187 | color: inherit;
188 | text-decoration: none;
189 | }
190 |
191 | pre,
192 | code,
193 | kbd,
194 | samp {
195 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
196 | font-size: 1em;
197 | direction: ltr /* rtl:ignore */;
198 | unicode-bidi: bidi-override;
199 | }
200 |
201 | pre {
202 | display: block;
203 | margin-top: 0;
204 | margin-bottom: 1rem;
205 | overflow: auto;
206 | font-size: 0.875em;
207 | }
208 | pre code {
209 | font-size: inherit;
210 | color: inherit;
211 | word-break: normal;
212 | }
213 |
214 | code {
215 | font-size: 0.875em;
216 | color: #d63384;
217 | word-wrap: break-word;
218 | }
219 | a > code {
220 | color: inherit;
221 | }
222 |
223 | kbd {
224 | padding: 0.2rem 0.4rem;
225 | font-size: 0.875em;
226 | color: #fff;
227 | background-color: #212529;
228 | border-radius: 0.2rem;
229 | }
230 | kbd kbd {
231 | padding: 0;
232 | font-size: 1em;
233 | font-weight: 700;
234 | }
235 |
236 | figure {
237 | margin: 0 0 1rem;
238 | }
239 |
240 | img,
241 | svg {
242 | vertical-align: middle;
243 | }
244 |
245 | table {
246 | caption-side: bottom;
247 | border-collapse: collapse;
248 | }
249 |
250 | caption {
251 | padding-top: 0.5rem;
252 | padding-bottom: 0.5rem;
253 | color: #6c757d;
254 | text-align: left;
255 | }
256 |
257 | th {
258 | text-align: inherit;
259 | text-align: -webkit-match-parent;
260 | }
261 |
262 | thead,
263 | tbody,
264 | tfoot,
265 | tr,
266 | td,
267 | th {
268 | border-color: inherit;
269 | border-style: solid;
270 | border-width: 0;
271 | }
272 |
273 | label {
274 | display: inline-block;
275 | }
276 |
277 | button {
278 | border-radius: 0;
279 | }
280 |
281 | button:focus:not(:focus-visible) {
282 | outline: 0;
283 | }
284 |
285 | input,
286 | button,
287 | select,
288 | optgroup,
289 | textarea {
290 | margin: 0;
291 | font-family: inherit;
292 | font-size: inherit;
293 | line-height: inherit;
294 | }
295 |
296 | button,
297 | select {
298 | text-transform: none;
299 | }
300 |
301 | [role=button] {
302 | cursor: pointer;
303 | }
304 |
305 | select {
306 | word-wrap: normal;
307 | }
308 | select:disabled {
309 | opacity: 1;
310 | }
311 |
312 | [list]::-webkit-calendar-picker-indicator {
313 | display: none;
314 | }
315 |
316 | button,
317 | [type=button],
318 | [type=reset],
319 | [type=submit] {
320 | -webkit-appearance: button;
321 | }
322 | button:not(:disabled),
323 | [type=button]:not(:disabled),
324 | [type=reset]:not(:disabled),
325 | [type=submit]:not(:disabled) {
326 | cursor: pointer;
327 | }
328 |
329 | ::-moz-focus-inner {
330 | padding: 0;
331 | border-style: none;
332 | }
333 |
334 | textarea {
335 | resize: vertical;
336 | }
337 |
338 | fieldset {
339 | min-width: 0;
340 | padding: 0;
341 | margin: 0;
342 | border: 0;
343 | }
344 |
345 | legend {
346 | float: left;
347 | width: 100%;
348 | padding: 0;
349 | margin-bottom: 0.5rem;
350 | font-size: calc(1.275rem + 0.3vw);
351 | line-height: inherit;
352 | }
353 | @media (min-width: 1200px) {
354 | legend {
355 | font-size: 1.5rem;
356 | }
357 | }
358 | legend + * {
359 | clear: left;
360 | }
361 |
362 | ::-webkit-datetime-edit-fields-wrapper,
363 | ::-webkit-datetime-edit-text,
364 | ::-webkit-datetime-edit-minute,
365 | ::-webkit-datetime-edit-hour-field,
366 | ::-webkit-datetime-edit-day-field,
367 | ::-webkit-datetime-edit-month-field,
368 | ::-webkit-datetime-edit-year-field {
369 | padding: 0;
370 | }
371 |
372 | ::-webkit-inner-spin-button {
373 | height: auto;
374 | }
375 |
376 | [type=search] {
377 | outline-offset: -2px;
378 | -webkit-appearance: textfield;
379 | }
380 |
381 | /* rtl:raw:
382 | [type="tel"],
383 | [type="url"],
384 | [type="email"],
385 | [type="number"] {
386 | direction: ltr;
387 | }
388 | */
389 | ::-webkit-search-decoration {
390 | -webkit-appearance: none;
391 | }
392 |
393 | ::-webkit-color-swatch-wrapper {
394 | padding: 0;
395 | }
396 |
397 | ::file-selector-button {
398 | font: inherit;
399 | }
400 |
401 | ::-webkit-file-upload-button {
402 | font: inherit;
403 | -webkit-appearance: button;
404 | }
405 |
406 | output {
407 | display: inline-block;
408 | }
409 |
410 | iframe {
411 | border: 0;
412 | }
413 |
414 | summary {
415 | display: list-item;
416 | cursor: pointer;
417 | }
418 |
419 | progress {
420 | vertical-align: baseline;
421 | }
422 |
423 | [hidden] {
424 | display: none !important;
425 | }
426 |
427 | /*# sourceMappingURL=bootstrap-reboot.css.map */
--------------------------------------------------------------------------------
/src/BulkRename/Resources/SharedResource.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | An error occurred while processing your request.
122 |
123 |
124 | <strong>The Development environment shouldn't be enabled for deployed applications.</strong>
125 | It can result in displaying sensitive information from exceptions to end users.
126 | For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
127 | and restarting the app.
128 |
129 |
130 | Development Mode
131 |
132 |
133 | Error
134 |
135 |
136 | History
137 |
138 |
139 | Home
140 |
141 |
142 | Load History
143 |
144 |
145 | New Name
146 |
147 |
148 | Old Name
149 |
150 |
151 | Preview Renaming of TV-Shows
152 |
153 |
154 | Renamed on
155 |
156 |
157 | Request
158 |
159 |
160 | Series
161 |
162 |
163 | Submit Renaming
164 |
165 |
166 | Successfully renamed files
167 |
168 |
169 | Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
170 |
171 |
172 | Welcome to BulkRename!
173 |
174 |
--------------------------------------------------------------------------------