Compare commits
251 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33e6db98cd | |||
| 77d47f0b28 | |||
| d66c57cfd0 | |||
| 12e48d069b | |||
| dbdb1cbd06 | |||
| 8b479b57e5 | |||
| 663b0f3931 | |||
| 18d8223d7e | |||
| 1a215f3176 | |||
| ddad642a88 | |||
| 339957bd91 | |||
| 051801f337 | |||
| 46922dd259 | |||
| e2adb25da0 | |||
| 5146658e0f | |||
| dece680221 | |||
| 48ee9d6b0e | |||
| 3e82a48f4d | |||
| bd448ef05a | |||
| 82efa14b7a | |||
| 61f25edafc | |||
| 436875e8ec | |||
| ac087a87d3 | |||
| 903ce09426 | |||
| 02dc510078 | |||
| ffefe417db | |||
| 00adffc8b3 | |||
| 2ec3278637 | |||
| 1f52a0c186 | |||
| 40f7d820a6 | |||
| 19f7e6bd88 | |||
| dea3553885 | |||
| d83d23803a | |||
| fae5dd517e | |||
| 247c96ac6e | |||
| 360e06bbc5 | |||
| ef48baf7ae | |||
| 8e06a88a2c | |||
| 6898d9b270 | |||
| 0aa8a5c243 | |||
| c5326fc5c9 | |||
| 9431e878f3 | |||
| 3ac71a37e8 | |||
| 0ac5e0daf4 | |||
| f3a7ada586 | |||
| d835008c67 | |||
| bd4f3ad0d9 | |||
| 8083edddb9 | |||
| 886942c4e8 | |||
| 7576c8138e | |||
| 296adf70b4 | |||
| b3a6c70ae7 | |||
| ef28173511 | |||
| b25cb9995f | |||
| 38e8e1cf9c | |||
| f25641b2a7 | |||
| 4101a1cb17 | |||
| 4911c2718c | |||
| aa5ad746eb | |||
| c88ee55e8d | |||
| 01340e0316 | |||
| 219f855d86 | |||
| 46c4f9d3a1 | |||
| 17011a1e76 | |||
| 9ceb5305d5 | |||
| c8a92434af | |||
| 9aea19cee5 | |||
| 0c0c60b83f | |||
| df10358088 | |||
| 7cbfe01c0b | |||
| 4863052a08 | |||
| e97e8caab8 | |||
| cb09681cb0 | |||
| b9b7c9f965 | |||
| 5936f72263 | |||
| ba27c7e8e8 | |||
| b4ef37a61a | |||
| 6c4185dc01 | |||
| f51a086137 | |||
| b8ff4a5906 | |||
| f79e63a13b | |||
| b0aae7248c | |||
| 467b12491c | |||
| 286907b89e | |||
| bd0c1cd932 | |||
| daaa2bf911 | |||
| bdb6b48e3f | |||
| f97bc0fbb6 | |||
| f7aee1c12f | |||
| 48dbd5c570 | |||
| 51397d7c28 | |||
| 69a8cb71ea | |||
| 184a01b64d | |||
| 0689bf4f31 | |||
| 3300dbbe5e | |||
| 5784ea96e7 | |||
| a77763ee89 | |||
| ef409298e8 | |||
| bad0d15c1c | |||
| 35aac2cdec | |||
| 6101b4edde | |||
| 13ed3757da | |||
| d086244820 | |||
| 597a10fc5d | |||
| 1a80e94bca | |||
| 27eaa8d4af | |||
| e0eb8473ee | |||
| 6c5d2c8057 | |||
| c3fc21a2b3 | |||
| 2596043a94 | |||
| 7ea156d422 | |||
| 28fe2f7d22 | |||
| 0478f01113 | |||
| ddf29c5875 | |||
| ad82270dcc | |||
| 6c8a033a56 | |||
| 44ff0e16e6 | |||
| c00120bb9a | |||
| d800a4198d | |||
| 1f3d305a55 | |||
| addad5b7dc | |||
| 22204a62d4 | |||
| 9d2fb0dd03 | |||
| 2b9ffdb6b9 | |||
| 0439e5782e | |||
| 740abb1426 | |||
| df12f19c53 | |||
| 9470892118 | |||
| 91b925f95c | |||
| b3e9617aff | |||
| d7aeae3d26 | |||
| f6d8a980a6 | |||
| 8fa1499a2f | |||
| 74e0a4d999 | |||
| c58cffe4a2 | |||
| 95af144ae9 | |||
| 1aade7c27c | |||
| e8538cc035 | |||
| 65ef97d9ba | |||
| 20fc78419e | |||
| dda988f0ea | |||
| ac42c8ae26 | |||
| c5c974e996 | |||
| a6948d492a | |||
| b3e6f0fd22 | |||
| ca60b1e24d | |||
| a279be1b47 | |||
| 198fff508d | |||
| 3fd918d381 | |||
| 35c7fb60fd | |||
| 79623a8a67 | |||
| d34dcb0b27 | |||
| 37d8b71d5d | |||
| 81f44024f7 | |||
| e477c8b0b3 | |||
| 76461e866b | |||
| 3f22e727f6 | |||
| c606314c0f | |||
| 37e12cd0be | |||
| b063928732 | |||
| 111a3ca6ac | |||
| 7176c99264 | |||
| 30c32da5e6 | |||
| 23608ca70c | |||
| 3ab01a2164 | |||
| d9e608329d | |||
| 367ad70be7 | |||
| e868a57979 | |||
| ab3b04c165 | |||
| 5cd7cba4fb | |||
| 6b0377bb71 | |||
| 1589f9105c | |||
| 597b9bbfcb | |||
| d84cfe001f | |||
| 6cccf84043 | |||
| abfa20ec31 | |||
| 3f65ec3318 | |||
| b67855b7a3 | |||
| 0299de4fdd | |||
| ba09e945f0 | |||
| 2eb050e663 | |||
| 7499c9a19c | |||
| e309afa1a0 | |||
| 5d71df36bd | |||
| 7be76f10d6 | |||
| 22a62421f0 | |||
| be3032192a | |||
| 37dc9989c4 | |||
| eb7a0cb85d | |||
| 36188f00b0 | |||
| 3fc76f61d0 | |||
| 0e7ac68c24 | |||
| 0d3a413708 | |||
| 41fafaf7ae | |||
| 0789295701 | |||
| bb81298e84 | |||
| bd6e2477b5 | |||
| acae05d287 | |||
| c81488c3fe | |||
| 2688e281a7 | |||
| b853b8e89f | |||
| 0558fbd7ff | |||
| 8471339578 | |||
| 3d95c3a744 | |||
| 523a093776 | |||
| 49d5e9a29e | |||
| 221438c5fc | |||
| a29de2f910 | |||
| 8de155c1a4 | |||
| 846da6763a | |||
| 40c9357adb | |||
| fba91b203c | |||
| 93891a6a71 | |||
| 5091f60bb2 | |||
| 1bbcf47d07 | |||
| 7ef98207cb | |||
| 646b3c6efb | |||
| aef659cc92 | |||
| a0660c6696 | |||
| eb0166ae7a | |||
| 44cde95b47 | |||
| 746072a65d | |||
| f17aadccda | |||
| b2ea92940e | |||
| e18a668e13 | |||
| db2dafd40c | |||
| a3614fee0d | |||
| 8a66943f4a | |||
| 2368ea7cf8 | |||
| ebd2bf149c | |||
| 7dfc288f93 | |||
| 01990394dd | |||
| bec9923c6a | |||
| 52f29a5fa1 | |||
| 179f36e2a2 | |||
| 378e8f5cd9 | |||
| 523283acd0 | |||
| 4e9578bed4 | |||
| 14adc2a3b3 | |||
| 7117414fb1 | |||
| d37cbe6309 | |||
| ebae2ceb73 | |||
| f4c2437e53 | |||
| a05c71ec85 | |||
| 02fd789f20 | |||
| e68dda81d2 | |||
| d3faf1d10a | |||
| 3e8ba73af2 | |||
| ee08552ad6 | |||
| 5772bcc7f2 | |||
| b6c62af015 |
@@ -142,3 +142,4 @@ build/ui-docs.zip
|
||||
build/csharp-docs.zip
|
||||
build/msbuild.log
|
||||
.vs/
|
||||
src/packages/
|
||||
+15
-14
@@ -2,21 +2,23 @@ version: '{build}'
|
||||
shallow_clone: true
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd build
|
||||
SET SLN=%CD%
|
||||
|
||||
SET SRC=%SLN%\src
|
||||
|
||||
SET PACKAGES=%SRC%\packages
|
||||
|
||||
CD build
|
||||
|
||||
SET "release="
|
||||
|
||||
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i"
|
||||
|
||||
SET nuGetFolder=C:\Users\appveyor\.nuget\packages
|
||||
|
||||
..\src\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL
|
||||
|
||||
..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
|
||||
|
||||
IF EXIST ..\src\umbraco.businesslogic\packages.config ..\src\.nuget\NuGet.exe install ..\src\umbraco.businesslogic\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
|
||||
|
||||
..\src\.nuget\NuGet.exe install ..\src\Umbraco.Core\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
|
||||
ECHO "Restoring NuGet into %PACKAGES%"
|
||||
|
||||
%SRC%\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL
|
||||
|
||||
%SRC%\.nuget\NuGet.exe restore %SRC%\umbraco.sln -Verbosity Quiet -NonInteractive -PackagesDirectory %PACKAGES%
|
||||
|
||||
ECHO Building Release %release% build%APPVEYOR_BUILD_NUMBER%
|
||||
|
||||
@@ -24,13 +26,12 @@ build_script:
|
||||
|
||||
SET MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MsBuild.exe"
|
||||
|
||||
XCOPY "..\src\Umbraco.Tests\unit-test-log4net.CI.config" "..\src\Umbraco.Tests\unit-test-log4net.config" /Y
|
||||
XCOPY "%SRC%\Umbraco.Tests\unit-test-log4net.CI.config" "%SRC%\Umbraco.Tests\unit-test-log4net.config" /Y
|
||||
|
||||
%MSBUILD% "..\src\Umbraco.Tests\Umbraco.Tests.csproj" /consoleloggerparameters:Summary;ErrorsOnly
|
||||
%MSBUILD% "%SLN%/src/Umbraco.Tests/Umbraco.Tests.csproj" /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /p:NugetPackagesDirectory=%PACKAGES%
|
||||
|
||||
build.bat nopause %release% build%APPVEYOR_BUILD_NUMBER%
|
||||
build.bat -integration -release:%release% -comment:build%APPVEYOR_BUILD_NUMBER% -nugetfolder:%PACKAGES%
|
||||
|
||||
ECHO %PATH%
|
||||
test:
|
||||
assemblies: src\Umbraco.Tests\bin\Debug\Umbraco.Tests.dll
|
||||
artifacts:
|
||||
|
||||
+109
-30
@@ -1,31 +1,95 @@
|
||||
@ECHO OFF
|
||||
|
||||
:: UMBRACO BUILD FILE
|
||||
|
||||
|
||||
:: ensure we have UmbracoVersion.txt
|
||||
IF NOT EXIST UmbracoVersion.txt (
|
||||
ECHO UmbracoVersion.txt missing!
|
||||
GOTO :showerror
|
||||
ECHO UmbracoVersion.txt is missing!
|
||||
GOTO error
|
||||
)
|
||||
|
||||
REM Get the version and comment from UmbracoVersion.txt lines 2 and 3
|
||||
SET "release="
|
||||
SET "comment="
|
||||
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i"
|
||||
FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED comment SET "comment=%%i"
|
||||
SET RELEASE=
|
||||
SET COMMENT=
|
||||
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED RELEASE SET RELEASE=%%i
|
||||
FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED COMMENT SET COMMENT=%%i
|
||||
|
||||
REM If there's arguments on the command line overrule UmbracoVersion.txt and use that as the version
|
||||
IF [%2] NEQ [] (SET release=%2)
|
||||
IF [%3] NEQ [] (SET comment=%3) ELSE (IF [%2] NEQ [] (SET "comment="))
|
||||
REM process args
|
||||
|
||||
REM Get the "is continuous integration" from the parameters
|
||||
SET "isci=0"
|
||||
IF [%1] NEQ [] (SET isci=1)
|
||||
SET INTEGRATION=0
|
||||
SET nuGetFolder=%CD%\..\src\packages
|
||||
SET SKIPNUGET=0
|
||||
|
||||
SET version=%release%
|
||||
IF [%comment%] EQU [] (SET version=%release%) ELSE (SET version=%release%-%comment%)
|
||||
:processArgs
|
||||
|
||||
ECHO.
|
||||
ECHO Building Umbraco %version%
|
||||
ECHO.
|
||||
:: grab the first parameter as a whole eg "/action:start"
|
||||
:: end if no more parameter
|
||||
SET SWITCHPARSE=%1
|
||||
IF [%SWITCHPARSE%] == [] goto endProcessArgs
|
||||
|
||||
ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %release%
|
||||
:: get switch and value
|
||||
SET SWITCH=
|
||||
SET VALUE=
|
||||
FOR /F "tokens=1,* delims=: " %%a IN ("%SWITCHPARSE%") DO SET SWITCH=%%a& SET VALUE=%%b
|
||||
|
||||
:: route arg
|
||||
IF '%SWITCH%'=='/release' GOTO argRelease
|
||||
IF '%SWITCH%'=='-release' GOTO argRelease
|
||||
IF '%SWITCH%'=='/comment' GOTO argComment
|
||||
IF '%SWITCH%'=='-comment' GOTO argComment
|
||||
IF '%SWITCH%'=='/integration' GOTO argIntegration
|
||||
IF '%SWITCH%'=='-integration' GOTO argIntegration
|
||||
IF '%SWITCH%'=='/nugetfolder' GOTO argNugetFolder
|
||||
IF '%SWITCH%'=='-nugetfolder' GOTO argNugetFolder
|
||||
IF '%SWITCH%'=='/skipnuget' GOTO argSkipNuget
|
||||
IF '%SWITCH%'=='-skipnuget' GOTO argSkipNuget
|
||||
ECHO "Invalid switch %SWITCH%"
|
||||
GOTO error
|
||||
|
||||
:: handle each arg
|
||||
|
||||
:argRelease
|
||||
set RELEASE=%VALUE%
|
||||
SHIFT
|
||||
goto processArgs
|
||||
|
||||
:argComment
|
||||
SET COMMENT=%VALUE%
|
||||
SHIFT
|
||||
GOTO processArgs
|
||||
|
||||
:argIntegration
|
||||
SET INTEGRATION=1
|
||||
SHIFT
|
||||
GOTO processArgs
|
||||
|
||||
:argNugetFolder
|
||||
SET nuGetFolder=%VALUE%
|
||||
SHIFT
|
||||
GOTO processArgs
|
||||
|
||||
:argSkipNuget
|
||||
SET SKIPNUGET=1
|
||||
SHIFT
|
||||
GOTO processArgs
|
||||
|
||||
:endProcessArgs
|
||||
|
||||
REM run
|
||||
|
||||
SET VERSION=%RELEASE%
|
||||
IF [%COMMENT%] EQU [] (SET VERSION=%RELEASE%) ELSE (SET VERSION=%RELEASE%-%COMMENT%)
|
||||
|
||||
ECHO ################################################################
|
||||
ECHO Building Umbraco %VERSION%
|
||||
ECHO ################################################################
|
||||
|
||||
SET MSBUILDPATH=C:\Program Files (x86)\MSBuild\14.0\Bin
|
||||
SET MSBUILD="%MSBUILDPATH%\MsBuild.exe"
|
||||
SET PATH="%MSBUILDPATH%";%PATH%
|
||||
|
||||
ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %RELEASE%
|
||||
|
||||
ECHO.
|
||||
ECHO Removing the belle build folder and bower_components folder to make sure everything is clean as a whistle
|
||||
@@ -35,10 +99,10 @@ RD ..\src\Umbraco.Web.UI.Client\bower_components /Q /S
|
||||
ECHO.
|
||||
ECHO Removing existing built files to make sure everything is clean as a whistle
|
||||
RMDIR /Q /S _BuildOutput
|
||||
DEL /F /Q UmbracoCms.*.zip
|
||||
DEL /F /Q UmbracoExamine.*.zip
|
||||
DEL /F /Q UmbracoCms.*.nupkg
|
||||
DEL /F /Q webpihash.txt
|
||||
DEL /F /Q UmbracoCms.*.zip 2>NUL
|
||||
DEL /F /Q UmbracoExamine.*.zip 2>NUL
|
||||
DEL /F /Q UmbracoCms.*.nupkg 2>NUL
|
||||
DEL /F /Q webpihash.txt 2>NUL
|
||||
|
||||
ECHO.
|
||||
ECHO Making sure Git is in the path so that the build can succeed
|
||||
@@ -47,11 +111,21 @@ CALL InstallGit.cmd
|
||||
REM Adding the default Git path so that if it's installed it can actually be found
|
||||
REM This is necessary because SETLOCAL is on in InstallGit.cmd so that one might find Git,
|
||||
REM but the path setting is lost due to SETLOCAL
|
||||
path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH%
|
||||
SET PATH="C:\Program Files (x86)\Git\cmd";"C:\Program Files\Git\cmd";%PATH%
|
||||
|
||||
ECHO.
|
||||
ECHO Making sure we have a web.config
|
||||
IF NOT EXIST %CD%\..\src\Umbraco.Web.UI\web.config COPY %CD%\..\src\Umbraco.Web.UI\web.Template.config %CD%\..\src\Umbraco.Web.UI\web.config
|
||||
IF NOT EXIST "%CD%\..\src\Umbraco.Web.UI\web.config" COPY "%CD%\..\src\Umbraco.Web.UI\web.Template.config" "%CD%\..\src\Umbraco.Web.UI\web.config"
|
||||
|
||||
ECHO.
|
||||
ECHO Reporting NuGet version
|
||||
"%CD%\..\src\.nuget\NuGet.exe" help | findstr "^NuGet Version:"
|
||||
|
||||
ECHO.
|
||||
ECHO Restoring NuGet packages
|
||||
ECHO Into %nuGetFolder%
|
||||
"%CD%\..\src\.nuget\NuGet.exe" restore "%CD%\..\src\umbraco.sln" -Verbosity Quiet -NonInteractive -PackagesDirectory "%nuGetFolder%"
|
||||
IF ERRORLEVEL 1 GOTO :error
|
||||
|
||||
ECHO.
|
||||
ECHO.
|
||||
@@ -60,13 +134,15 @@ ECHO This takes a few minutes and logging is set to report warnings
|
||||
ECHO and errors only so it might seems like nothing is happening for a while.
|
||||
ECHO You can check the msbuild.log file for progress.
|
||||
ECHO.
|
||||
%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /p:NugetPackagesDirectory=%nuGetFolder% /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /fileLogger
|
||||
IF ERRORLEVEL 1 GOTO :error
|
||||
%MSBUILD% "Build.proj" /p:BUILD_RELEASE=%RELEASE% /p:BUILD_COMMENT=%COMMENT% /p:NugetPackagesDirectory="%nuGetFolder%" /consoleloggerparameters:Summary;ErrorsOnly /fileLogger
|
||||
IF ERRORLEVEL 1 GOTO error
|
||||
|
||||
ECHO.
|
||||
ECHO Setting node_modules folder to hidden to prevent VS13 from crashing on it while loading the websites project
|
||||
attrib +h ..\src\Umbraco.Web.UI.Client\node_modules
|
||||
|
||||
IF %SKIPNUGET% EQU 1 GOTO success
|
||||
|
||||
ECHO.
|
||||
ECHO Adding Web.config transform files to the NuGet package
|
||||
REN .\_BuildOutput\WebApp\Views\Web.config Web.config.transform
|
||||
@@ -74,22 +150,25 @@ REN .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform
|
||||
|
||||
ECHO.
|
||||
ECHO Packing the NuGet release files
|
||||
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% -Symbols -Verbosity quiet
|
||||
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %version% -Verbosity quiet
|
||||
IF ERRORLEVEL 1 GOTO :error
|
||||
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %VERSION% -Symbols -Verbosity quiet
|
||||
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %VERSION% -Verbosity quiet
|
||||
IF ERRORLEVEL 1 GOTO error
|
||||
|
||||
:success
|
||||
ECHO.
|
||||
ECHO No errors were detected!
|
||||
ECHO There may still be some in the output, which you would need to investigate.
|
||||
ECHO Warnings are usually normal.
|
||||
ECHO.
|
||||
ECHO.
|
||||
GOTO :EOF
|
||||
|
||||
:error
|
||||
|
||||
ECHO.
|
||||
ECHO Errors were detected!
|
||||
ECHO.
|
||||
|
||||
REM don't pause if continuous integration else the build server waits forever
|
||||
REM before cancelling the build (and, there is noone to read the output anyways)
|
||||
IF isci NEQ 1 PAUSE
|
||||
IF %INTEGRATION% NEQ 1 PAUSE
|
||||
|
||||
+18
-20
@@ -1,8 +1,8 @@
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!--
|
||||
<!--
|
||||
****************************************************
|
||||
INCLUDES
|
||||
INCLUDES
|
||||
*****************************************************
|
||||
-->
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
{
|
||||
using (var cryptoProvider = new SHA1CryptoServiceProvider())
|
||||
{
|
||||
|
||||
|
||||
var fileHash = cryptoProvider.ComputeHash(stream);
|
||||
|
||||
|
||||
using (TextWriter w = new StreamWriter(OutputFile, false))
|
||||
{
|
||||
w.WriteLine(string.Join("", fileHash.Select(b => b.ToString("x2"))));
|
||||
@@ -52,9 +52,9 @@
|
||||
</UsingTask>
|
||||
|
||||
|
||||
<!--
|
||||
<!--
|
||||
****************************************************
|
||||
VARIABLES
|
||||
VARIABLES
|
||||
*****************************************************
|
||||
-->
|
||||
|
||||
@@ -102,9 +102,9 @@
|
||||
<SystemFolders Include="$(WebAppFolder)Views" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
****************************************************
|
||||
TARGETS
|
||||
TARGETS
|
||||
*****************************************************
|
||||
-->
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
<Message Text="Finished zipping to build\$(BuildFolder)\$(buildDate)-$(BuildZipFileName)" Importance="high" />
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="CreateSystemFolders" DependsOnTargets="CopyBelleBuild" Inputs="@(SystemFolders)" Outputs="%(Identity).Dummy">
|
||||
<MakeDir Directories="@(SystemFolders)" />
|
||||
</Target>
|
||||
@@ -231,7 +231,7 @@
|
||||
DestinationFiles="@(CustomLanguageFiles->'$(ConfigsFolder)Lang\%(RecursiveDir)%(Filename)%(Extension)')"
|
||||
OverwriteReadOnlyFiles="true"
|
||||
SkipUnchangedFiles="false" />
|
||||
|
||||
|
||||
<Copy SourceFiles="@(WebConfigTransformFile)"
|
||||
DestinationFiles="$(ConfigsFolder)Web.config.transform"
|
||||
OverwriteReadOnlyFiles="true"
|
||||
@@ -264,14 +264,12 @@
|
||||
<Message Text="Compiling web project to build\$(BuildFolder)" Importance="high" />
|
||||
<!-- For UseWPP_CopyWebApplication=True see http://stackoverflow.com/questions/1983575/copywebapplication-with-web-config-transformations -->
|
||||
<!-- Build the Umbraco.Web.UI project -->
|
||||
<MSBuild Projects="..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" Properties="WarningLevel=0;Configuration=$(BuildConfiguration);UseWPP_CopyWebApplication=True;PipelineDependsOnBuild=False;OutDir=$(SolutionBinFolderAbsolutePath);WebProjectOutputDir=$(WebAppFolderAbsolutePath);Verbosity=minimal" Targets="Clean;Rebuild;" BuildInParallel="False" ToolsVersion="4.0" UnloadProjectsOnCompletion="False">
|
||||
</MSBuild>
|
||||
<MSBuild Projects="..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" Properties="WarningLevel=0;Configuration=$(BuildConfiguration);UseWPP_CopyWebApplication=True;PipelineDependsOnBuild=False;OutDir=$(SolutionBinFolderAbsolutePath);WebProjectOutputDir=$(WebAppFolderAbsolutePath);Verbosity=minimal" Targets="Clean;Rebuild;" BuildInParallel="False" ToolsVersion="4.0" UnloadProjectsOnCompletion="False" />
|
||||
|
||||
<!-- DONE -->
|
||||
<Message Text="Finished compiling projects" Importance="high" />
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="SetVersionNumber" Condition="'$(BUILD_RELEASE)'!=''">
|
||||
<PropertyGroup>
|
||||
<NewVersion>$(BUILD_RELEASE)</NewVersion>
|
||||
@@ -307,34 +305,34 @@
|
||||
ReplacementText="AssemblyFileVersion("$(BUILD_RELEASE)")"/>
|
||||
|
||||
<!--This updates the AssemblyInformationalVersion for the solution to the umbraco version and comment-->
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'!=''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)-$(BUILD_COMMENT)")"/>
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'==''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)")"/>
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_NIGHTLY)'!=''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)-$(BUILD_NIGHTLY)")"/>
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'!='' AND '$(BUILD_NIGHTLY)'!=''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)")"/>
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'=='' AND '$(BUILD_NIGHTLY)'==''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)")"/>
|
||||
|
||||
|
||||
<!--This updates the copyright year-->
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyCopyright\("Copyright © Umbraco (\d{4})"\)"
|
||||
ReplacementText="AssemblyCopyright("Copyright © Umbraco $([System.DateTime]::Now.ToString(`yyyy`))")"/>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
@ECHO OFF
|
||||
SETLOCAL
|
||||
:: SETLOCAL is on, so changes to the path not persist to the actual user's path
|
||||
REM SETLOCAL is on, so changes to the path not persist to the actual user's path
|
||||
|
||||
git.exe --version
|
||||
IF %ERRORLEVEL%==9009 GOTO :trydefaultpath
|
||||
REM OK, DONE
|
||||
GOTO :EOF
|
||||
:: Git is installed, no need to to anything else
|
||||
|
||||
:trydefaultpath
|
||||
PATH=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH%
|
||||
git.exe --version
|
||||
IF %ERRORLEVEL%==9009 GOTO :showerror
|
||||
REM OK, DONE
|
||||
GOTO :EOF
|
||||
:: Git is installed, no need to to anything else
|
||||
|
||||
:showerror
|
||||
ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
<dependency id="semver" version="[1.1.2, 2.0.0)" />
|
||||
<dependency id="UrlRewritingNet" version="[2.0.7, 3.0.0)" />
|
||||
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
|
||||
<dependency id="Markdown" version="[1.14.4, 2.0.0)" />
|
||||
<dependency id="Markdown" version="[1.14.4, 2.0.0)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.6.0, 5.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.5, 4.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
|
||||
7.5.7
|
||||
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
|
||||
7.6.0
|
||||
alpha038
|
||||
Binary file not shown.
@@ -180,12 +180,16 @@ namespace SqlCE4Umbraco
|
||||
/// <returns>The return value of the command.</returns>
|
||||
protected override object ExecuteScalar(string commandText, SqlCeParameter[] parameters)
|
||||
{
|
||||
#if DEBUG && DebugDataLayer
|
||||
#if DEBUG && DebugDataLayer
|
||||
// Log Query Execution
|
||||
Trace.TraceInformation(GetType().Name + " SQL ExecuteScalar: " + commandText);
|
||||
#endif
|
||||
|
||||
return SqlCeApplicationBlock.ExecuteScalar(ConnectionString, CommandType.Text, commandText, parameters);
|
||||
#endif
|
||||
using (var cc = UseCurrentConnection)
|
||||
{
|
||||
return SqlCeApplicationBlock.ExecuteScalar(
|
||||
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
|
||||
CommandType.Text, commandText, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -198,12 +202,17 @@ namespace SqlCE4Umbraco
|
||||
/// </returns>
|
||||
protected override int ExecuteNonQuery(string commandText, SqlCeParameter[] parameters)
|
||||
{
|
||||
#if DEBUG && DebugDataLayer
|
||||
#if DEBUG && DebugDataLayer
|
||||
// Log Query Execution
|
||||
Trace.TraceInformation(GetType().Name + " SQL ExecuteNonQuery: " + commandText);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return SqlCeApplicationBlock.ExecuteNonQuery(ConnectionString, CommandType.Text, commandText, parameters);
|
||||
using (var cc = UseCurrentConnection)
|
||||
{
|
||||
return SqlCeApplicationBlock.ExecuteNonQuery(
|
||||
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
|
||||
CommandType.Text, commandText, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -216,13 +225,17 @@ namespace SqlCE4Umbraco
|
||||
/// </returns>
|
||||
protected override IRecordsReader ExecuteReader(string commandText, SqlCeParameter[] parameters)
|
||||
{
|
||||
#if DEBUG && DebugDataLayer
|
||||
#if DEBUG && DebugDataLayer
|
||||
// Log Query Execution
|
||||
Trace.TraceInformation(GetType().Name + " SQL ExecuteReader: " + commandText);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader(ConnectionString, CommandType.Text,
|
||||
commandText, parameters));
|
||||
using (var cc = UseCurrentConnection)
|
||||
{
|
||||
return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader(
|
||||
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
|
||||
CommandType.Text, commandText, parameters));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
@@ -26,30 +24,61 @@ namespace SqlCE4Umbraco
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
object retVal;
|
||||
|
||||
try
|
||||
{
|
||||
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
|
||||
using (var conn = SqlCeContextGuardian.Open(connectionString))
|
||||
{
|
||||
using (SqlCeCommand cmd = new SqlCeCommand(commandText, conn))
|
||||
{
|
||||
AttachParameters(cmd, commandParameters);
|
||||
Debug.WriteLine("---------------------------------SCALAR-------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
retVal = cmd.ExecuteScalar();
|
||||
}
|
||||
return ExecuteScalarTry(conn, null, commandText, commandParameters);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
|
||||
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee);
|
||||
}
|
||||
}
|
||||
|
||||
public static object ExecuteScalar(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ExecuteScalarTry(conn, trx, commandText, commandParameters);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee);
|
||||
}
|
||||
}
|
||||
|
||||
public static object ExecuteScalar(
|
||||
SqlCeConnection conn,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
return ExecuteScalar(conn, null, commandType, commandText, commandParameters);
|
||||
}
|
||||
|
||||
private static object ExecuteScalarTry(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
object retVal;
|
||||
using (var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx))
|
||||
{
|
||||
AttachParameters(cmd, commandParameters);
|
||||
Debug.WriteLine("---------------------------------SCALAR-------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
retVal = cmd.ExecuteScalar();
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -66,49 +95,10 @@ namespace SqlCE4Umbraco
|
||||
{
|
||||
try
|
||||
{
|
||||
int rowsAffected;
|
||||
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
|
||||
using (var conn = SqlCeContextGuardian.Open(connectionString))
|
||||
{
|
||||
// this is for multiple queries in the installer
|
||||
if (commandText.Trim().StartsWith("!!!"))
|
||||
{
|
||||
commandText = commandText.Trim().Trim('!');
|
||||
string[] commands = commandText.Split('|');
|
||||
string currentCmd = String.Empty;
|
||||
|
||||
foreach (string cmd in commands)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentCmd = cmd;
|
||||
if (!String.IsNullOrWhiteSpace(cmd))
|
||||
{
|
||||
SqlCeCommand c = new SqlCeCommand(cmd, conn);
|
||||
c.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("*******************************************************************");
|
||||
Debug.WriteLine(currentCmd);
|
||||
Debug.WriteLine(e);
|
||||
Debug.WriteLine("*******************************************************************");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
SqlCeCommand cmd = new SqlCeCommand(commandText, conn);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
rowsAffected = cmd.ExecuteNonQuery();
|
||||
}
|
||||
return ExecuteNonQueryTry(conn, null, commandText, commandParameters);
|
||||
}
|
||||
|
||||
return rowsAffected;
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
@@ -116,6 +106,74 @@ namespace SqlCE4Umbraco
|
||||
}
|
||||
}
|
||||
|
||||
public static int ExecuteNonQuery(
|
||||
SqlCeConnection conn,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
return ExecuteNonQuery(conn, null, commandType, commandText, commandParameters);
|
||||
}
|
||||
|
||||
public static int ExecuteNonQuery(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ExecuteNonQueryTry(conn, trx, commandText, commandParameters);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
throw new SqlCeProviderException("Error running NonQuery: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static int ExecuteNonQueryTry(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
// this is for multiple queries in the installer
|
||||
if (commandText.Trim().StartsWith("!!!"))
|
||||
{
|
||||
commandText = commandText.Trim().Trim('!');
|
||||
var commands = commandText.Split('|');
|
||||
var currentCmd = string.Empty;
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentCmd = command;
|
||||
if (string.IsNullOrWhiteSpace(command)) continue;
|
||||
var c = trx == null ? new SqlCeCommand(command, conn) : new SqlCeCommand(command, conn, trx);
|
||||
c.ExecuteNonQuery();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("*******************************************************************");
|
||||
Debug.WriteLine(currentCmd);
|
||||
Debug.WriteLine(e);
|
||||
Debug.WriteLine("*******************************************************************");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
var cmd = new SqlCeCommand(commandText, conn);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
var rowsAffected = cmd.ExecuteNonQuery();
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -133,25 +191,8 @@ namespace SqlCE4Umbraco
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine("---------------------------------READER-------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
SqlCeDataReader reader;
|
||||
SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString);
|
||||
|
||||
try
|
||||
{
|
||||
SqlCeCommand cmd = new SqlCeCommand(commandText, conn);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
|
||||
}
|
||||
catch
|
||||
{
|
||||
conn.Close();
|
||||
throw;
|
||||
}
|
||||
|
||||
return reader;
|
||||
var conn = SqlCeContextGuardian.Open(connectionString);
|
||||
return ExecuteReaderTry(conn, null, commandText, commandParameters);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
@@ -159,30 +200,71 @@ namespace SqlCE4Umbraco
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VerifyConnection(string connectionString)
|
||||
public static SqlCeDataReader ExecuteReader(
|
||||
SqlCeConnection conn,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
bool isConnected = false;
|
||||
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
|
||||
{
|
||||
isConnected = conn.State == ConnectionState.Open;
|
||||
}
|
||||
|
||||
return isConnected;
|
||||
return ExecuteReader(conn, commandType, commandText, commandParameters);
|
||||
}
|
||||
|
||||
private static void AttachParameters(SqlCeCommand command, SqlCeParameter[] commandParameters)
|
||||
public static SqlCeDataReader ExecuteReader(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
foreach (SqlCeParameter parameter in commandParameters)
|
||||
try
|
||||
{
|
||||
return ExecuteReaderTry(conn, trx, commandText, commandParameters);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
throw new SqlCeProviderException("Error running Reader: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static SqlCeDataReader ExecuteReaderTry(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
Debug.WriteLine("---------------------------------READER-------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
|
||||
try
|
||||
{
|
||||
var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
|
||||
}
|
||||
catch
|
||||
{
|
||||
conn.Close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VerifyConnection(string connectionString)
|
||||
{
|
||||
using (var conn = SqlCeContextGuardian.Open(connectionString))
|
||||
{
|
||||
return conn.State == ConnectionState.Open;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AttachParameters(SqlCeCommand command, IEnumerable<SqlCeParameter> commandParameters)
|
||||
{
|
||||
foreach (var parameter in commandParameters)
|
||||
{
|
||||
if ((parameter.Direction == ParameterDirection.InputOutput) && (parameter.Value == null))
|
||||
{
|
||||
parameter.Value = DBNull.Value;
|
||||
}
|
||||
command.Parameters.Add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.5.7")]
|
||||
[assembly: AssemblyInformationalVersion("7.5.7")]
|
||||
[assembly: AssemblyFileVersion("7.6.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.6.0-alpha038")]
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static class ByteArrayExtensions
|
||||
{
|
||||
private static readonly char[] BytesToHexStringLookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
|
||||
public static string ToHexString(this byte[] bytes)
|
||||
{
|
||||
int i = 0, p = 0, bytesLength = bytes.Length;
|
||||
var chars = new char[bytesLength * 2];
|
||||
while (i < bytesLength)
|
||||
{
|
||||
var b = bytes[i++];
|
||||
chars[p++] = BytesToHexStringLookup[b / 0x10];
|
||||
chars[p++] = BytesToHexStringLookup[b % 0x10];
|
||||
}
|
||||
return new string(chars, 0, chars.Length);
|
||||
}
|
||||
|
||||
public static string ToHexString(this byte[] bytes, char separator, int blockSize, int blockCount)
|
||||
{
|
||||
int p = 0, bytesLength = bytes.Length, count = 0, size = 0;
|
||||
var chars = new char[bytesLength * 2 + blockCount];
|
||||
for (var i = 0; i < bytesLength; i++)
|
||||
{
|
||||
var b = bytes[i++];
|
||||
chars[p++] = BytesToHexStringLookup[b / 0x10];
|
||||
chars[p++] = BytesToHexStringLookup[b % 0x10];
|
||||
if (count == blockCount) continue;
|
||||
if (++size < blockSize) continue;
|
||||
|
||||
chars[p++] = '/';
|
||||
size = 0;
|
||||
count++;
|
||||
}
|
||||
return new string(chars, 0, chars.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,7 @@ namespace Umbraco.Core.Cache
|
||||
|
||||
value = result.Value; // will not throw (safe lazy)
|
||||
var eh = value as ExceptionHolder;
|
||||
if (eh != null) throw eh.Exception; // throw once!
|
||||
if (eh != null) throw new Exception("Exception while creating a value.", eh.Exception); // throw once!
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public class UmbracoVersion
|
||||
{
|
||||
private static readonly Version Version = new Version("7.5.7");
|
||||
private static readonly Version Version = new Version("7.6.0");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
|
||||
/// Gets the version comment (like beta or RC).
|
||||
/// </summary>
|
||||
/// <value>The version comment.</value>
|
||||
public static string CurrentComment { get { return ""; } }
|
||||
public static string CurrentComment { get { return "alpha038"; } }
|
||||
|
||||
// Get the version of the umbraco.dll by looking at a class in that dll
|
||||
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
|
||||
|
||||
@@ -25,9 +25,6 @@
|
||||
public const int DefaultContentListViewDataTypeId = -95;
|
||||
public const int DefaultMediaListViewDataTypeId = -96;
|
||||
public const int DefaultMembersListViewDataTypeId = -97;
|
||||
|
||||
// identifiers for lock objects
|
||||
public const int ServersLock = -331;
|
||||
}
|
||||
|
||||
public static class DatabaseProviders
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Configuration;
|
||||
using System.Xml.Linq;
|
||||
using Semver;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -30,7 +30,6 @@ namespace Umbraco.Core
|
||||
private readonly ILogger _logger;
|
||||
private readonly SqlSyntaxProviders _syntaxProviders;
|
||||
private bool _configured;
|
||||
private readonly object _locker = new object();
|
||||
private string _connectionString;
|
||||
private string _providerName;
|
||||
private DatabaseSchemaResult _result;
|
||||
@@ -46,7 +45,7 @@ namespace Umbraco.Core
|
||||
: this(factory, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[]
|
||||
{
|
||||
new MySqlSyntaxProvider(LoggerResolver.Current.Logger),
|
||||
new SqlCeSyntaxProvider(),
|
||||
new SqlCeSyntaxProvider(),
|
||||
new SqlServerSyntaxProvider()
|
||||
}))
|
||||
{
|
||||
@@ -86,21 +85,60 @@ namespace Umbraco.Core
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
public List<UmbracoDatabase> Databases
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = _factory as DefaultDatabaseFactory;
|
||||
if (factory == null) throw new NotSupportedException();
|
||||
return factory.Databases;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public ISqlSyntaxProvider SqlSyntax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Database"/> object for doing CRUD operations
|
||||
/// against custom tables that resides in the Umbraco database.
|
||||
/// Gets an "ambient" database for doing CRUD operations against custom tables that resides in the Umbraco database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should not be used for CRUD operations or queries against the
|
||||
/// standard Umbraco tables! Use the Public services for that.
|
||||
/// <para>Should not be used for operation against standard Umbraco tables; as services should be used instead.</para>
|
||||
/// <para>Gets or creates an "ambient" database that is either stored in http context + available for the whole
|
||||
/// request + auto-disposed at the end of the request, or stored in call context if there is no http context - in which
|
||||
/// case it *must* be explicitely disposed (which will remove it from call context).</para>
|
||||
/// </remarks>
|
||||
public virtual UmbracoDatabase Database
|
||||
{
|
||||
get { return _factory.CreateDatabase(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the "ambient" database (if any) and by a new temp database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The returned IDisposable *must* be diposed in order to properly dispose the temp database and
|
||||
/// restore the original "ambient" database (if any).</para>
|
||||
/// <para>This is to be used in background tasks to ensure that they have an "ambient" database which
|
||||
/// will be properly removed from call context and does not interfere with anything else. In most case
|
||||
/// it is not replacing anything, just temporarily installing a database in context.</para>
|
||||
/// </remarks>
|
||||
public virtual IDisposable UseSafeDatabase(bool force = false)
|
||||
{
|
||||
var factory = _factory as DefaultDatabaseFactory;
|
||||
if (factory == null) throw new NotSupportedException();
|
||||
|
||||
if (DefaultDatabaseFactory.HasAmbientDatabase)
|
||||
{
|
||||
return force
|
||||
? new UsingDatabase(DefaultDatabaseFactory.DetachAmbientDatabase(), factory.CreateDatabase())
|
||||
: new UsingDatabase(null, null);
|
||||
}
|
||||
|
||||
// create a new, temp, database (will be disposed with UsingDatabase)
|
||||
return new UsingDatabase(null, factory.CreateDatabase());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the database has been configured
|
||||
/// </summary>
|
||||
@@ -210,7 +248,7 @@ namespace Umbraco.Core
|
||||
// since it's been like this for quite some time
|
||||
//using (var engine = new SqlCeEngine(connectionString))
|
||||
//{
|
||||
// engine.CreateDatabase();
|
||||
// engine.CreateDatabase();
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -251,14 +289,14 @@ namespace Umbraco.Core
|
||||
/// <param name="password">Database Password</param>
|
||||
/// <param name="databaseProvider">Type of the provider to be used (Sql, Sql Azure, Sql Ce, MySql)</param>
|
||||
public void ConfigureDatabaseConnection(string server, string databaseName, string user, string password, string databaseProvider)
|
||||
{
|
||||
{
|
||||
string providerName;
|
||||
var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out providerName);
|
||||
|
||||
SaveConnectionString(connectionString, providerName);
|
||||
Initialize(providerName);
|
||||
}
|
||||
|
||||
|
||||
public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName)
|
||||
{
|
||||
providerName = Constants.DatabaseProviders.SqlServer;
|
||||
@@ -289,18 +327,18 @@ namespace Umbraco.Core
|
||||
|
||||
public string GetIntegratedSecurityDatabaseConnectionString(string server, string databaseName)
|
||||
{
|
||||
return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
|
||||
return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
|
||||
}
|
||||
|
||||
internal string BuildAzureConnectionString(string server, string databaseName, string user, string password)
|
||||
{
|
||||
if (server.Contains(".") && ServerStartsWithTcp(server) == false)
|
||||
server = string.Format("tcp:{0}", server);
|
||||
|
||||
|
||||
if (server.Contains(".") == false && ServerStartsWithTcp(server))
|
||||
{
|
||||
string serverName = server.Contains(",")
|
||||
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
|
||||
string serverName = server.Contains(",")
|
||||
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
|
||||
: server;
|
||||
|
||||
var portAddition = string.Empty;
|
||||
@@ -310,10 +348,10 @@ namespace Umbraco.Core
|
||||
|
||||
server = string.Format("{0}.database.windows.net{1}", serverName, portAddition);
|
||||
}
|
||||
|
||||
|
||||
if (ServerStartsWithTcp(server) == false)
|
||||
server = string.Format("tcp:{0}.database.windows.net", server);
|
||||
|
||||
|
||||
if (server.Contains(",") == false)
|
||||
server = string.Format("{0},1433", server);
|
||||
|
||||
@@ -357,7 +395,7 @@ namespace Umbraco.Core
|
||||
|
||||
_connectionString = connectionString;
|
||||
_providerName = providerName;
|
||||
|
||||
|
||||
var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root));
|
||||
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
|
||||
var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single();
|
||||
@@ -402,7 +440,7 @@ namespace Umbraco.Core
|
||||
connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString;
|
||||
}
|
||||
Initialize(providerName, connString);
|
||||
|
||||
|
||||
}
|
||||
else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false)
|
||||
{
|
||||
@@ -439,7 +477,7 @@ namespace Umbraco.Core
|
||||
|
||||
//Remove the legacy connection string, so we don't end up in a loop if something goes wrong.
|
||||
GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -458,15 +496,15 @@ namespace Umbraco.Core
|
||||
{
|
||||
if (_syntaxProviders != null)
|
||||
{
|
||||
SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName);
|
||||
SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName);
|
||||
}
|
||||
else if (SqlSyntax == null)
|
||||
{
|
||||
throw new InvalidOperationException("No " + typeof(ISqlSyntaxProvider) + " specified or no " + typeof(SqlSyntaxProviders) + " instance specified");
|
||||
}
|
||||
|
||||
|
||||
SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax;
|
||||
|
||||
|
||||
_configured = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -506,7 +544,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
internal Result CreateDatabaseSchemaAndData(ApplicationContext applicationContext)
|
||||
{
|
||||
{
|
||||
try
|
||||
{
|
||||
var readyForInstall = CheckReadyForInstall();
|
||||
@@ -524,7 +562,7 @@ namespace Umbraco.Core
|
||||
// If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks
|
||||
// for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive.
|
||||
|
||||
/*
|
||||
/*
|
||||
var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
if (supportsCaseInsensitiveQueries == false)
|
||||
{
|
||||
@@ -542,9 +580,9 @@ namespace Umbraco.Core
|
||||
message = GetResultMessageForMySql();
|
||||
|
||||
var schemaResult = ValidateDatabaseSchema();
|
||||
|
||||
|
||||
var installedSchemaVersion = schemaResult.DetermineInstalledVersion();
|
||||
|
||||
|
||||
//If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing
|
||||
if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0)))
|
||||
{
|
||||
@@ -563,9 +601,9 @@ namespace Umbraco.Core
|
||||
message = "<p>Upgrading database, this may take some time...</p>";
|
||||
return new Result
|
||||
{
|
||||
RequiresUpgrade = true,
|
||||
Message = message,
|
||||
Success = true,
|
||||
RequiresUpgrade = true,
|
||||
Message = message,
|
||||
Success = true,
|
||||
Percentage = "30"
|
||||
};
|
||||
}
|
||||
@@ -593,7 +631,7 @@ namespace Umbraco.Core
|
||||
_logger.Info<DatabaseContext>("Database upgrade started");
|
||||
|
||||
var database = new UmbracoDatabase(_connectionString, ProviderName, _logger);
|
||||
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
|
||||
var message = GetResultMessageForMySql();
|
||||
|
||||
@@ -601,32 +639,32 @@ namespace Umbraco.Core
|
||||
|
||||
var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion());
|
||||
|
||||
var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
|
||||
|
||||
var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
|
||||
|
||||
var targetVersion = UmbracoVersion.Current;
|
||||
|
||||
|
||||
//In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will
|
||||
// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
|
||||
// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
|
||||
// migrations are run for the target version
|
||||
if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0)))
|
||||
{
|
||||
//set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute
|
||||
installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision());
|
||||
}
|
||||
|
||||
|
||||
//Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum
|
||||
// version detected between the schema installed and the migrations listed in the migration table.
|
||||
// version detected between the schema installed and the migrations listed in the migration table.
|
||||
// If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what
|
||||
// is declared in the web.config.
|
||||
|
||||
|
||||
var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus)
|
||||
//Take the minimum version between the detected schema version and the installed migration version
|
||||
? new[] {installedSchemaVersion, installedMigrationVersion}.Min()
|
||||
//Take the minimum version between the installed migration version and the version specified in the config
|
||||
: new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min();
|
||||
|
||||
//Ok, another edge case here. If the current version is a pre-release,
|
||||
// then we want to ensure all migrations for the current release are executed.
|
||||
//Ok, another edge case here. If the current version is a pre-release,
|
||||
// then we want to ensure all migrations for the current release are executed.
|
||||
if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision());
|
||||
@@ -761,7 +799,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString());
|
||||
var filePath = datasource.Replace("Data Source=", string.Empty);
|
||||
sqlCeDatabaseExists = File.Exists(filePath);
|
||||
sqlCeDatabaseExists = File.Exists(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,5 +813,28 @@ namespace Umbraco.Core
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class UsingDatabase : IDisposable
|
||||
{
|
||||
private readonly UmbracoDatabase _orig;
|
||||
private readonly UmbracoDatabase _temp;
|
||||
|
||||
public UsingDatabase(UmbracoDatabase orig, UmbracoDatabase temp)
|
||||
{
|
||||
_orig = orig;
|
||||
_temp = temp;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_temp != null)
|
||||
{
|
||||
_temp.Dispose();
|
||||
if (_orig != null)
|
||||
DefaultDatabaseFactory.AttachAmbientDatabase(_orig);
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
@@ -47,5 +48,35 @@ namespace Umbraco.Core.Events
|
||||
if (eventHandler != null)
|
||||
eventHandler(sender, args);
|
||||
}
|
||||
}
|
||||
|
||||
// moves the last handler that was added to an instance event, to first position
|
||||
public static void PromoteLastHandler(object sender, string eventName)
|
||||
{
|
||||
var fieldInfo = sender.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
|
||||
PromoteLastHandler(sender, fieldInfo);
|
||||
}
|
||||
|
||||
// moves the last handler that was added to a static event, to first position
|
||||
public static void PromoteLastHandler<TSender>(string eventName)
|
||||
{
|
||||
var fieldInfo = typeof(TSender).GetField(eventName, BindingFlags.Static | BindingFlags.NonPublic);
|
||||
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
|
||||
PromoteLastHandler(null, fieldInfo);
|
||||
}
|
||||
|
||||
private static void PromoteLastHandler(object sender, FieldInfo fieldInfo)
|
||||
{
|
||||
var d = fieldInfo.GetValue(sender) as Delegate;
|
||||
if (d == null) return;
|
||||
|
||||
var l = d.GetInvocationList();
|
||||
var x = l[l.Length - 1];
|
||||
for (var i = l.Length - 1; i > 0; i--)
|
||||
l[i] = l[i - 1];
|
||||
l[0] = x;
|
||||
|
||||
fieldInfo.SetValue(sender, Delegate.Combine(l));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public interface ICompletable : IDisposable
|
||||
{
|
||||
void Complete();
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,14 @@ namespace Umbraco.Core.IO
|
||||
throw new ArgumentException("Retries must be greater than zero");
|
||||
}
|
||||
|
||||
// GetSize has been added to IFileSystem2 but not IFileSystem
|
||||
// this is implementing GetSize for IFileSystem, the old way
|
||||
public static long GetSize(this IFileSystem fs, string path)
|
||||
{
|
||||
// if we reach this point, fs is *not* IFileSystem2
|
||||
// so it's not FileSystemWrapper nor shadow nor anything we know
|
||||
// so... fall back to the old & inefficient method
|
||||
|
||||
using (var file = fs.OpenFile(path))
|
||||
{
|
||||
return file.Length;
|
||||
|
||||
@@ -4,8 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Umbraco.Core.CodeAnnotations;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
@@ -13,8 +11,27 @@ namespace Umbraco.Core.IO
|
||||
public class FileSystemProviderManager
|
||||
{
|
||||
private readonly FileSystemProvidersSection _config;
|
||||
private readonly WeakSet<ShadowWrapper> _wrappers = new WeakSet<ShadowWrapper>();
|
||||
|
||||
#region Singleton
|
||||
// actual well-known filesystems returned by properties
|
||||
private readonly IFileSystem2 _macroPartialFileSystem;
|
||||
private readonly IFileSystem2 _partialViewsFileSystem;
|
||||
private readonly IFileSystem2 _stylesheetsFileSystem;
|
||||
private readonly IFileSystem2 _scriptsFileSystem;
|
||||
private readonly IFileSystem2 _xsltFileSystem;
|
||||
private readonly IFileSystem2 _masterPagesFileSystem;
|
||||
private readonly IFileSystem2 _mvcViewsFileSystem;
|
||||
|
||||
// when shadowing is enabled, above filesystems, as wrappers
|
||||
private readonly ShadowWrapper _macroPartialFileSystemWrapper;
|
||||
private readonly ShadowWrapper _partialViewsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _stylesheetsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _scriptsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _xsltFileSystemWrapper;
|
||||
private readonly ShadowWrapper _masterPagesFileSystemWrapper;
|
||||
private readonly ShadowWrapper _mvcViewsFileSystemWrapper;
|
||||
|
||||
#region Singleton & Constructor
|
||||
|
||||
private static readonly FileSystemProviderManager Instance = new FileSystemProviderManager();
|
||||
|
||||
@@ -23,109 +40,224 @@ namespace Umbraco.Core.IO
|
||||
get { return Instance; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal FileSystemProviderManager()
|
||||
{
|
||||
_config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
|
||||
_config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
|
||||
|
||||
_macroPartialFileSystem = new PhysicalFileSystem(SystemDirectories.MacroPartials);
|
||||
_partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews);
|
||||
_stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css);
|
||||
_scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
|
||||
_xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt);
|
||||
_masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
|
||||
_mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
|
||||
|
||||
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials");
|
||||
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials");
|
||||
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css");
|
||||
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts");
|
||||
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt");
|
||||
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages");
|
||||
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views");
|
||||
|
||||
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
|
||||
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
|
||||
/// </summary>
|
||||
private class ProviderConstructionInfo
|
||||
#region Well-Known FileSystems
|
||||
|
||||
public IFileSystem2 MacroPartialsFileSystem { get { return _macroPartialFileSystem; } }
|
||||
public IFileSystem2 PartialViewsFileSystem { get { return _partialViewsFileSystem; } }
|
||||
public IFileSystem2 StylesheetsFileSystem { get { return _stylesheetsFileSystem; } }
|
||||
public IFileSystem2 ScriptsFileSystem { get { return _scriptsFileSystem; } }
|
||||
public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } }
|
||||
public IFileSystem2 MasterPagesFileSystem { get { return _masterPagesFileSystem; } }
|
||||
public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } }
|
||||
public MediaFileSystem MediaFileSystem { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Providers
|
||||
|
||||
/// <summary>
|
||||
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
|
||||
/// </summary>
|
||||
private class ProviderConstructionInfo
|
||||
{
|
||||
public object[] Parameters { get; set; }
|
||||
public ConstructorInfo Constructor { get; set; }
|
||||
public string ProviderAlias { get; set; }
|
||||
//public string ProviderAlias { get; set; }
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, ProviderConstructionInfo> _providerLookup = new ConcurrentDictionary<string, ProviderConstructionInfo>();
|
||||
private readonly ConcurrentDictionary<Type, string> _wrappedProviderLookup = new ConcurrentDictionary<Type, string>();
|
||||
private readonly ConcurrentDictionary<Type, string> _aliases = new ConcurrentDictionary<Type, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying (non-typed) file system provider for the alias specified
|
||||
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
|
||||
/// </summary>
|
||||
/// <param name="alias"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// It is recommended to use the typed GetFileSystemProvider method instead to get a strongly typed provider instance.
|
||||
/// </remarks>
|
||||
/// <param name="alias">The alias of the strongly-typed filesystem.</param>
|
||||
/// <returns>The non-typed filesystem supporting the strongly-typed filesystem with the specified alias.</returns>
|
||||
/// <remarks>This method should not be used directly, used <see cref="GetFileSystemProvider{TFileSystem}"/> instead.</remarks>
|
||||
public IFileSystem GetUnderlyingFileSystemProvider(string alias)
|
||||
{
|
||||
//either get the constructor info from cache or create it and add to cache
|
||||
// either get the constructor info from cache or create it and add to cache
|
||||
var ctorInfo = _providerLookup.GetOrAdd(alias, s =>
|
||||
{
|
||||
// get config
|
||||
var providerConfig = _config.Providers[s];
|
||||
if (providerConfig == null)
|
||||
throw new ArgumentException(string.Format("No provider found with the alias '{0}'", s));
|
||||
throw new ArgumentException(string.Format("No provider found with alias {0}.", s));
|
||||
|
||||
// get the filesystem type
|
||||
var providerType = Type.GetType(providerConfig.Type);
|
||||
if (providerType == null)
|
||||
throw new InvalidOperationException(string.Format("Could not find type '{0}'", providerConfig.Type));
|
||||
throw new InvalidOperationException(string.Format("Could not find type {0}.", providerConfig.Type));
|
||||
|
||||
// ensure it implements IFileSystem
|
||||
if (providerType.IsAssignableFrom(typeof (IFileSystem)))
|
||||
throw new InvalidOperationException(string.Format("The type '{0}' does not implement IFileSystem", providerConfig.Type));
|
||||
throw new InvalidOperationException(string.Format("Type {0} does not implement IFileSystem.", providerType.FullName));
|
||||
|
||||
// find a ctor matching the config parameters
|
||||
var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0;
|
||||
var constructor = providerType.GetConstructors()
|
||||
.SingleOrDefault(x => x.GetParameters().Count() == paramCount
|
||||
&& x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
|
||||
var constructor = providerType.GetConstructors().SingleOrDefault(x
|
||||
=> x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException(string.Format("Could not find constructor for type '{0}' which accepts {1} parameters", providerConfig.Type, paramCount));
|
||||
throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount));
|
||||
|
||||
var parameters = new object[paramCount];
|
||||
for (var i = 0; i < paramCount; i++)
|
||||
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
|
||||
if (providerConfig.Parameters != null) // keeps ReSharper happy
|
||||
for (var i = 0; i < paramCount; i++)
|
||||
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
|
||||
|
||||
//return the new constructor info class to cache so we don't have to do this again.
|
||||
return new ProviderConstructionInfo()
|
||||
return new ProviderConstructionInfo
|
||||
{
|
||||
Constructor = constructor,
|
||||
Parameters = parameters,
|
||||
ProviderAlias = s
|
||||
//ProviderAlias = s
|
||||
};
|
||||
});
|
||||
|
||||
var fs = (IFileSystem)ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
|
||||
return fs;
|
||||
// create the fs and return
|
||||
return (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the strongly typed file system provider
|
||||
/// Gets a strongly-typed filesystem.
|
||||
/// </summary>
|
||||
/// <typeparam name="TProviderTypeFilter"></typeparam>
|
||||
/// <returns></returns>
|
||||
public TProviderTypeFilter GetFileSystemProvider<TProviderTypeFilter>()
|
||||
where TProviderTypeFilter : FileSystemWrapper
|
||||
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
|
||||
/// <returns>A strongly-typed filesystem of the specified type.</returns>
|
||||
public TFileSystem GetFileSystemProvider<TFileSystem>()
|
||||
where TFileSystem : FileSystemWrapper
|
||||
{
|
||||
//get the alias for the type from cache or look it up and add it to the cache, then we don't have to reflect each time
|
||||
var alias = _wrappedProviderLookup.GetOrAdd(typeof (TProviderTypeFilter), fsType =>
|
||||
// deal with known types - avoid infinite loops!
|
||||
if (typeof(TFileSystem) == typeof(MediaFileSystem) && MediaFileSystem != null)
|
||||
return MediaFileSystem as TFileSystem; // else create and return
|
||||
|
||||
// get/cache the alias for the filesystem type
|
||||
var alias = _aliases.GetOrAdd(typeof (TFileSystem), fsType =>
|
||||
{
|
||||
//validate the ctor
|
||||
var constructor = fsType.GetConstructors()
|
||||
.SingleOrDefault(x =>
|
||||
x.GetParameters().Count() == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
|
||||
// validate the ctor
|
||||
var constructor = fsType.GetConstructors().SingleOrDefault(x
|
||||
=> x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException("The type of " + fsType + " must inherit from FileSystemWrapper and must have a constructor that accepts one parameter of type " + typeof(IFileSystem));
|
||||
|
||||
var attr =
|
||||
(FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).
|
||||
SingleOrDefault();
|
||||
throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + ".");
|
||||
|
||||
// find the attribute and get the alias
|
||||
var attr = (FileSystemProviderAttribute) fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault();
|
||||
if (attr == null)
|
||||
throw new InvalidOperationException(string.Format("The provider type filter '{0}' is missing the required FileSystemProviderAttribute", typeof(FileSystemProviderAttribute).FullName));
|
||||
throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute.");
|
||||
|
||||
return attr.Alias;
|
||||
});
|
||||
|
||||
|
||||
// gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return
|
||||
// so we are double-wrapping here
|
||||
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
|
||||
var innerFs = GetUnderlyingFileSystemProvider(alias);
|
||||
var outputFs = Activator.CreateInstance(typeof (TProviderTypeFilter), innerFs);
|
||||
return (TProviderTypeFilter)outputFs;
|
||||
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias);
|
||||
var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), innerFs);
|
||||
_wrappers.Add(shadowWrapper); // keeping a weak reference to the wrapper
|
||||
return fs;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shadow
|
||||
|
||||
// note
|
||||
// shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one
|
||||
// global shadow for the entire application, so great care should be taken to ensure that the
|
||||
// application is *not* doing anything else when using a shadow.
|
||||
// shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would
|
||||
// be created directly (via ctor) or via GetFileSystemProvider<T> is *not* shadowed.
|
||||
|
||||
// shadow must be enabled in an app event handler before anything else ie before any filesystem
|
||||
// is actually created and used - after, it is too late - enabling shadow has a neglictible perfs
|
||||
// impact.
|
||||
// NO! by the time an app event handler is instanciated it is already too late, see note in ctor.
|
||||
//internal void EnableShadow()
|
||||
//{
|
||||
// if (_mvcViewsFileSystem != null) // test one of the fs...
|
||||
// throw new InvalidOperationException("Cannot enable shadow once filesystems have been created.");
|
||||
// _shadowEnabled = true;
|
||||
//}
|
||||
|
||||
public ICompletable Shadow(Guid id)
|
||||
{
|
||||
var typed = _wrappers.ToArray();
|
||||
var wrappers = new ShadowWrapper[typed.Length + 7];
|
||||
var i = 0;
|
||||
while (i < typed.Length) wrappers[i] = typed[i++];
|
||||
wrappers[i++] = _macroPartialFileSystemWrapper;
|
||||
wrappers[i++] = _partialViewsFileSystemWrapper;
|
||||
wrappers[i++] = _stylesheetsFileSystemWrapper;
|
||||
wrappers[i++] = _scriptsFileSystemWrapper;
|
||||
wrappers[i++] = _xsltFileSystemWrapper;
|
||||
wrappers[i++] = _masterPagesFileSystemWrapper;
|
||||
wrappers[i] = _mvcViewsFileSystemWrapper;
|
||||
|
||||
return ShadowFileSystemsScope.CreateScope(id, wrappers);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private class WeakSet<T>
|
||||
where T : class
|
||||
{
|
||||
private readonly HashSet<WeakReference<T>> _set = new HashSet<WeakReference<T>>();
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
lock (_set)
|
||||
{
|
||||
_set.Add(new WeakReference<T>(item));
|
||||
CollectLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
lock (_set)
|
||||
{
|
||||
CollectLocked();
|
||||
return _set.Select(x =>
|
||||
{
|
||||
T target;
|
||||
return x.TryGetTarget(out target) ? target : null;
|
||||
}).WhereNotNull().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectLocked()
|
||||
{
|
||||
_set.RemoveWhere(x =>
|
||||
{
|
||||
T target;
|
||||
return x.TryGetTarget(out target) == false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,93 +14,100 @@ namespace Umbraco.Core.IO
|
||||
///
|
||||
/// This abstract class just wraps the 'real' IFileSystem object passed in to its constructor.
|
||||
/// </remarks>
|
||||
public abstract class FileSystemWrapper : IFileSystem
|
||||
public abstract class FileSystemWrapper : IFileSystem2
|
||||
{
|
||||
private readonly IFileSystem _wrapped;
|
||||
|
||||
protected FileSystemWrapper(IFileSystem wrapped)
|
||||
protected FileSystemWrapper(IFileSystem wrapped)
|
||||
{
|
||||
_wrapped = wrapped;
|
||||
Wrapped = wrapped;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
internal IFileSystem Wrapped { get; set; }
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
return _wrapped.GetDirectories(path);
|
||||
return Wrapped.GetDirectories(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
_wrapped.DeleteDirectory(path);
|
||||
Wrapped.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
_wrapped.DeleteDirectory(path, recursive);
|
||||
Wrapped.DeleteDirectory(path, recursive);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return _wrapped.DirectoryExists(path);
|
||||
return Wrapped.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
_wrapped.AddFile(path, stream);
|
||||
Wrapped.AddFile(path, stream);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting)
|
||||
{
|
||||
_wrapped.AddFile(path, stream, overrideIfExists);
|
||||
Wrapped.AddFile(path, stream, overrideExisting);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
return _wrapped.GetFiles(path);
|
||||
return Wrapped.GetFiles(path);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
return _wrapped.GetFiles(path, filter);
|
||||
return Wrapped.GetFiles(path, filter);
|
||||
}
|
||||
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
return _wrapped.OpenFile(path);
|
||||
return Wrapped.OpenFile(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
_wrapped.DeleteFile(path);
|
||||
Wrapped.DeleteFile(path);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return _wrapped.FileExists(path);
|
||||
return Wrapped.FileExists(path);
|
||||
}
|
||||
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
return _wrapped.GetRelativePath(fullPathOrUrl);
|
||||
return Wrapped.GetRelativePath(fullPathOrUrl);
|
||||
}
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return _wrapped.GetFullPath(path);
|
||||
return Wrapped.GetFullPath(path);
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return _wrapped.GetUrl(path);
|
||||
return Wrapped.GetUrl(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
return _wrapped.GetLastModified(path);
|
||||
return Wrapped.GetLastModified(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
return _wrapped.GetCreated(path);
|
||||
return Wrapped.GetCreated(path);
|
||||
}
|
||||
}
|
||||
|
||||
// explicitely implementing - not breaking
|
||||
long IFileSystem2.GetSize(string path)
|
||||
{
|
||||
var wrapped2 = Wrapped as IFileSystem2;
|
||||
return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,6 @@ using System.IO;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
//TODO: There is no way to create a directory here without creating a file in a directory and then deleting it
|
||||
//TODO: Should probably implement a rename?
|
||||
|
||||
public interface IFileSystem
|
||||
{
|
||||
IEnumerable<string> GetDirectories(string path);
|
||||
@@ -16,7 +13,7 @@ namespace Umbraco.Core.IO
|
||||
void DeleteDirectory(string path, bool recursive);
|
||||
|
||||
bool DirectoryExists(string path);
|
||||
|
||||
|
||||
void AddFile(string path, Stream stream);
|
||||
|
||||
void AddFile(string path, Stream stream, bool overrideIfExists);
|
||||
@@ -31,7 +28,6 @@ namespace Umbraco.Core.IO
|
||||
|
||||
bool FileExists(string path);
|
||||
|
||||
|
||||
string GetRelativePath(string fullPathOrUrl);
|
||||
|
||||
string GetFullPath(string path);
|
||||
@@ -42,4 +38,17 @@ namespace Umbraco.Core.IO
|
||||
|
||||
DateTimeOffset GetCreated(string path);
|
||||
}
|
||||
|
||||
// this should be part of IFileSystem but we don't want to change the interface
|
||||
public interface IFileSystem2 : IFileSystem
|
||||
{
|
||||
long GetSize(string path);
|
||||
|
||||
// TODO: implement these
|
||||
//
|
||||
//void CreateDirectory(string path);
|
||||
//
|
||||
//// move or rename, directory or file
|
||||
//void Move(string source, string target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Media.Exif;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
@@ -14,59 +23,662 @@ namespace Umbraco.Core.IO
|
||||
public class MediaFileSystem : FileSystemWrapper
|
||||
{
|
||||
private readonly IContentSection _contentConfig;
|
||||
private readonly UploadAutoFillProperties _uploadAutoFillProperties;
|
||||
|
||||
public MediaFileSystem(IFileSystem wrapped)
|
||||
: this(wrapped, UmbracoConfig.For.UmbracoSettings().Content)
|
||||
{
|
||||
}
|
||||
private readonly object _folderCounterLock = new object();
|
||||
private long _folderCounter;
|
||||
private bool _folderCounterInitialized;
|
||||
|
||||
public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig) : base(wrapped)
|
||||
private static readonly Dictionary<int, string> DefaultSizes = new Dictionary<int, string>
|
||||
{
|
||||
{ 100, "thumb" },
|
||||
{ 500, "big-thumb" }
|
||||
};
|
||||
|
||||
public MediaFileSystem(IFileSystem wrapped)
|
||||
: this(wrapped, UmbracoConfig.For.UmbracoSettings().Content, ApplicationContext.Current.ProfilingLogger.Logger)
|
||||
{ }
|
||||
|
||||
public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger)
|
||||
: base(wrapped)
|
||||
{
|
||||
_contentConfig = contentConfig;
|
||||
_uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig);
|
||||
}
|
||||
|
||||
public string GetRelativePath(int propertyId, string fileName)
|
||||
internal UploadAutoFillProperties UploadAutoFillProperties { get { return _uploadAutoFillProperties; } }
|
||||
|
||||
// note - this is currently experimental / being developed
|
||||
//public static bool UseTheNewMediaPathScheme { get; set; }
|
||||
public const bool UseTheNewMediaPathScheme = false;
|
||||
|
||||
// none of the methods below are used in Core anymore
|
||||
|
||||
[Obsolete("This low-level method should NOT exist.")]
|
||||
public string GetRelativePath(int propertyId, string fileName)
|
||||
{
|
||||
var seperator = _contentConfig.UploadAllowDirectories
|
||||
var sep = _contentConfig.UploadAllowDirectories
|
||||
? Path.DirectorySeparatorChar
|
||||
: '-';
|
||||
|
||||
return propertyId.ToString(CultureInfo.InvariantCulture) + seperator + fileName;
|
||||
return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName;
|
||||
}
|
||||
|
||||
[Obsolete("This low-level method should NOT exist.", false)]
|
||||
public string GetRelativePath(string subfolder, string fileName)
|
||||
{
|
||||
var seperator = _contentConfig.UploadAllowDirectories
|
||||
var sep = _contentConfig.UploadAllowDirectories
|
||||
? Path.DirectorySeparatorChar
|
||||
: '-';
|
||||
|
||||
return subfolder + seperator + fileName;
|
||||
return subfolder + sep + fileName;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetThumbnails(string path)
|
||||
{
|
||||
var parentDirectory = Path.GetDirectoryName(path);
|
||||
var extension = Path.GetExtension(path);
|
||||
#region Media Path
|
||||
|
||||
return GetFiles(parentDirectory)
|
||||
.Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb"))
|
||||
.ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the file path of a media file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name.</param>
|
||||
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
|
||||
/// <param name="puid">The unique identifier of the property type owning the file.</param>
|
||||
/// <returns>The filesystem-relative path to the media file.</returns>
|
||||
/// <remarks>With the old media path scheme, this CREATES a new media path each time it is invoked.</remarks>
|
||||
public string GetMediaPath(string filename, Guid cuid, Guid puid)
|
||||
{
|
||||
filename = Path.GetFileName(filename);
|
||||
if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename");
|
||||
filename = IOHelper.SafeFileName(filename.ToLowerInvariant());
|
||||
|
||||
public void DeleteFile(string path, bool deleteThumbnails)
|
||||
{
|
||||
DeleteFile(path);
|
||||
string folder;
|
||||
if (UseTheNewMediaPathScheme == false)
|
||||
{
|
||||
// old scheme: filepath is "<int>/<filename>" OR "<int>-<filename>"
|
||||
// default media filesystem maps to "~/media/<filepath>"
|
||||
folder = GetNextFolder();
|
||||
}
|
||||
else
|
||||
{
|
||||
// new scheme: path is "<cuid>-<puid>/<filename>" OR "<cuid>-<puid>-<filename>"
|
||||
// default media filesystem maps to "~/media/<filepath>"
|
||||
// assumes that cuid and puid keys can be trusted - and that a single property type
|
||||
// for a single content cannot store two different files with the same name
|
||||
folder = Combine(cuid, puid).ToHexString(/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/...
|
||||
}
|
||||
|
||||
if (deleteThumbnails == false)
|
||||
return;
|
||||
var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
|
||||
? Path.Combine(folder, filename)
|
||||
: folder + "-" + filename;
|
||||
|
||||
DeleteThumbnails(path);
|
||||
}
|
||||
return filepath;
|
||||
}
|
||||
|
||||
public void DeleteThumbnails(string path)
|
||||
{
|
||||
GetThumbnails(path)
|
||||
.ForEach(DeleteFile);
|
||||
}
|
||||
}
|
||||
private static byte[] Combine(Guid guid1, Guid guid2)
|
||||
{
|
||||
var bytes1 = guid1.ToByteArray();
|
||||
var bytes2 = guid2.ToByteArray();
|
||||
var bytes = new byte[bytes1.Length];
|
||||
for (var i = 0; i < bytes1.Length; i++)
|
||||
bytes[i] = (byte)(bytes1[i] ^ bytes2[i]);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path of a media file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name.</param>
|
||||
/// <param name="prevpath">A previous file path.</param>
|
||||
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
|
||||
/// <param name="puid">The unique identifier of the property type owning the file.</param>
|
||||
/// <returns>The filesystem-relative path to the media file.</returns>
|
||||
/// <remarks>In the old, legacy, number-based scheme, we try to re-use the media folder
|
||||
/// specified by <paramref name="prevpath"/>. Else, we CREATE a new one. Each time we are invoked.</remarks>
|
||||
public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid)
|
||||
{
|
||||
if (UseTheNewMediaPathScheme || string.IsNullOrWhiteSpace(prevpath))
|
||||
return GetMediaPath(filename, cuid, puid);
|
||||
|
||||
filename = Path.GetFileName(filename);
|
||||
if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename");
|
||||
filename = IOHelper.SafeFileName(filename.ToLowerInvariant());
|
||||
|
||||
// old scheme, with a previous path
|
||||
// prevpath should be "<int>/<filename>" OR "<int>-<filename>"
|
||||
// and we want to reuse the "<int>" part, so try to find it
|
||||
|
||||
var sep = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? "/" : "-";
|
||||
var pos = prevpath.IndexOf(sep, StringComparison.Ordinal);
|
||||
var s = pos > 0 ? prevpath.Substring(0, pos) : null;
|
||||
int ignored;
|
||||
|
||||
var folder = (pos > 0 && int.TryParse(s, out ignored)) ? s : GetNextFolder();
|
||||
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
|
||||
? Path.Combine(folder, filename)
|
||||
: folder + "-" + filename;
|
||||
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next media folder in the original number-based scheme.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Should be private, is internal for legacy FileHandlerData which is obsolete.</remarks>
|
||||
internal string GetNextFolder()
|
||||
{
|
||||
EnsureFolderCounterIsInitialized();
|
||||
return Interlocked.Increment(ref _folderCounter).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void EnsureFolderCounterIsInitialized()
|
||||
{
|
||||
lock (_folderCounterLock)
|
||||
{
|
||||
if (_folderCounterInitialized) return;
|
||||
|
||||
_folderCounter = 1000; // seed
|
||||
var directories = GetDirectories("");
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
long folderNumber;
|
||||
if (long.TryParse(directory, out folderNumber) && folderNumber > _folderCounter)
|
||||
_folderCounter = folderNumber;
|
||||
}
|
||||
|
||||
// note: not multi-domains ie LB safe as another domain could create directories
|
||||
// while we read and parse them - don't fix, move to new scheme eventually
|
||||
|
||||
_folderCounterInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Associated Media Files
|
||||
|
||||
/// <summary>
|
||||
/// Stores a media file.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item owning the media file.</param>
|
||||
/// <param name="propertyType">The property type owning the media file.</param>
|
||||
/// <param name="filename">The media file name.</param>
|
||||
/// <param name="filestream">A stream containing the media bytes.</param>
|
||||
/// <param name="oldpath">An optional filesystem-relative filepath to the previous media file.</param>
|
||||
/// <returns>The filesystem-relative filepath to the media file.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The file is considered "owned" by the content/propertyType.</para>
|
||||
/// <para>If an <paramref name="oldpath"/> is provided then that file (and thumbnails) is deleted
|
||||
/// before the new file is saved, and depending on the media path scheme, the folder
|
||||
/// may be reused for the new file.</para>
|
||||
/// </remarks>
|
||||
public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyType == null) throw new ArgumentNullException("propertyType");
|
||||
if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Null or empty.", "filename");
|
||||
if (filestream == null) throw new ArgumentNullException("filestream");
|
||||
|
||||
// clear the old file, if any
|
||||
if (string.IsNullOrWhiteSpace(oldpath) == false)
|
||||
DeleteFile(oldpath, true);
|
||||
|
||||
// get the filepath, store the data
|
||||
// use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme
|
||||
var filepath = GetMediaPath(filename, oldpath, content.Key, propertyType.Key);
|
||||
AddFile(filepath, filestream);
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a media file.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem-relative path to the media file.</param>
|
||||
public new void DeleteFile(string filepath)
|
||||
{
|
||||
DeleteFile(filepath, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a media file.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item owning the copy of the media file.</param>
|
||||
/// <param name="propertyType">The property type owning the copy of the media file.</param>
|
||||
/// <param name="sourcepath">The filesystem-relative path to the source media file.</param>
|
||||
/// <returns>The filesystem-relative path to the copy of the media file.</returns>
|
||||
public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyType == null) throw new ArgumentNullException("propertyType");
|
||||
if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentException("Null or empty.", "sourcepath");
|
||||
|
||||
// ensure we have a file to copy
|
||||
if (FileExists(sourcepath) == false) return null;
|
||||
|
||||
// get the filepath
|
||||
var filename = Path.GetFileName(sourcepath);
|
||||
var filepath = GetMediaPath(filename, content.Key, propertyType.Key);
|
||||
this.CopyFile(sourcepath, filepath);
|
||||
CopyThumbnails(sourcepath, filepath);
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a property for a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <returns>The property.</returns>
|
||||
private static Property GetProperty(IContentBase content, string propertyTypeAlias)
|
||||
{
|
||||
var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
|
||||
if (property != null) return property;
|
||||
|
||||
var propertyType = content.GetContentType().CompositionPropertyTypes
|
||||
.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
|
||||
if (propertyType == null)
|
||||
throw new Exception("No property type exists with alias " + propertyTypeAlias + ".");
|
||||
|
||||
property = new Property(propertyType);
|
||||
content.Properties.Add(property);
|
||||
return property;
|
||||
}
|
||||
|
||||
public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream)
|
||||
{
|
||||
var property = GetProperty(content, propertyTypeAlias);
|
||||
var svalue = property.Value as string;
|
||||
var oldpath = svalue == null ? null : GetRelativePath(svalue);
|
||||
var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath);
|
||||
property.Value = GetUrl(filepath);
|
||||
SetUploadFile(content, property, filepath, filestream);
|
||||
}
|
||||
|
||||
public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath)
|
||||
{
|
||||
var property = GetProperty(content, propertyTypeAlias);
|
||||
var svalue = property.Value as string;
|
||||
var oldpath = svalue == null ? null : GetRelativePath(svalue); // FIXME DELETE?
|
||||
if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath)
|
||||
DeleteFile(oldpath, true);
|
||||
property.Value = GetUrl(filepath);
|
||||
using (var filestream = OpenFile(filepath))
|
||||
{
|
||||
SetUploadFile(content, property, filepath, filestream);
|
||||
}
|
||||
}
|
||||
|
||||
// sets a file for the FileUpload property editor
|
||||
// ie generates thumbnails and populates autofill properties
|
||||
private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream)
|
||||
{
|
||||
// check if file is an image (and supports resizing and thumbnails etc)
|
||||
var extension = Path.GetExtension(filepath);
|
||||
var isImage = IsImageFile(extension);
|
||||
|
||||
// specific stuff for images (thumbnails etc)
|
||||
if (isImage)
|
||||
{
|
||||
using (var image = Image.FromStream(filestream))
|
||||
{
|
||||
// use one image for all
|
||||
GenerateThumbnails(image, filepath, property.PropertyType);
|
||||
_uploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, image);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// will use filepath for extension, and filestream for length
|
||||
_uploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the file extension corresponds to an image.
|
||||
/// </summary>
|
||||
/// <param name="extension">The file extension.</param>
|
||||
/// <returns>A value indicating whether the file extension corresponds to an image.</returns>
|
||||
public bool IsImageFile(string extension)
|
||||
{
|
||||
if (extension == null) return false;
|
||||
extension = extension.TrimStart('.');
|
||||
return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dimensions of an image.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream containing the image bytes.</param>
|
||||
/// <returns>The dimension of the image.</returns>
|
||||
/// <remarks>First try with EXIF as it is faster and does not load the entire image
|
||||
/// in memory. Fallback to GDI which means loading the image in memory and thus
|
||||
/// use potentially large amounts of memory.</remarks>
|
||||
public Size GetDimensions(Stream stream)
|
||||
{
|
||||
//Try to load with exif
|
||||
try
|
||||
{
|
||||
var jpgInfo = ImageFile.FromStream(stream);
|
||||
|
||||
if (jpgInfo.Format != ImageFileFormat.Unknown
|
||||
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension)
|
||||
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension))
|
||||
{
|
||||
var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value);
|
||||
var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value);
|
||||
if (height > 0 && width > 0)
|
||||
{
|
||||
return new Size(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//We will just swallow, just means we can't read exif data, we don't want to log an error either
|
||||
}
|
||||
|
||||
//we have no choice but to try to read in via GDI
|
||||
using (var image = Image.FromStream(stream))
|
||||
{
|
||||
|
||||
var fileWidth = image.Width;
|
||||
var fileHeight = image.Height;
|
||||
return new Size(fileWidth, fileHeight);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Manage thumbnails
|
||||
|
||||
// note: this does not find 'custom' thumbnails?
|
||||
// will find _thumb and _big-thumb but NOT _custom?
|
||||
public IEnumerable<string> GetThumbnails(string path)
|
||||
{
|
||||
var parentDirectory = Path.GetDirectoryName(path);
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
return GetFiles(parentDirectory)
|
||||
.Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb"))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public void DeleteFile(string path, bool deleteThumbnails)
|
||||
{
|
||||
base.DeleteFile(path);
|
||||
|
||||
if (deleteThumbnails == false)
|
||||
return;
|
||||
|
||||
DeleteThumbnails(path);
|
||||
}
|
||||
|
||||
public void DeleteThumbnails(string path)
|
||||
{
|
||||
GetThumbnails(path)
|
||||
.ForEach(x => base.DeleteFile(x));
|
||||
}
|
||||
|
||||
public void CopyThumbnails(string sourcePath, string targetPath)
|
||||
{
|
||||
var targetPathBase = Path.GetDirectoryName(targetPath) ?? "";
|
||||
foreach (var sourceThumbPath in GetThumbnails(sourcePath))
|
||||
{
|
||||
var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? "";
|
||||
var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename);
|
||||
this.CopyFile(sourceThumbPath, targetThumbPath);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GenerateThumbnails
|
||||
|
||||
public IEnumerable<ResizedImage> GenerateThumbnails(
|
||||
Image image,
|
||||
string filepath,
|
||||
string preValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(preValue))
|
||||
return GenerateThumbnails(image, filepath);
|
||||
|
||||
var additionalSizes = new List<int>();
|
||||
var sep = preValue.Contains(",") ? "," : ";";
|
||||
var values = preValue.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var value in values)
|
||||
{
|
||||
int size;
|
||||
if (int.TryParse(value, out size))
|
||||
additionalSizes.Add(size);
|
||||
}
|
||||
|
||||
return GenerateThumbnails(image, filepath, additionalSizes);
|
||||
}
|
||||
|
||||
public IEnumerable<ResizedImage> GenerateThumbnails(
|
||||
Image image,
|
||||
string filepath,
|
||||
IEnumerable<int> additionalSizes = null)
|
||||
{
|
||||
var w = image.Width;
|
||||
var h = image.Height;
|
||||
|
||||
var sizes = additionalSizes == null ? DefaultSizes.Keys : DefaultSizes.Keys.Concat(additionalSizes);
|
||||
|
||||
// start with default sizes,
|
||||
// add additional sizes,
|
||||
// filter out duplicates,
|
||||
// filter out those that would be larger that the original image
|
||||
// and create the thumbnail
|
||||
return sizes
|
||||
.Distinct()
|
||||
.Where(x => w >= x && h >= x)
|
||||
.Select(x => GenerateResized(image, filepath, DefaultSizes.ContainsKey(x) ? DefaultSizes[x] : "", x))
|
||||
.ToList(); // now
|
||||
}
|
||||
|
||||
public IEnumerable<ResizedImage> GenerateThumbnails(
|
||||
Stream filestream,
|
||||
string filepath,
|
||||
PropertyType propertyType)
|
||||
{
|
||||
// get the original image from the original stream
|
||||
if (filestream.CanSeek) filestream.Seek(0, 0); // fixme - what if we cannot seek?
|
||||
using (var image = Image.FromStream(filestream))
|
||||
{
|
||||
return GenerateThumbnails(image, filepath, propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ResizedImage> GenerateThumbnails(
|
||||
Image image,
|
||||
string filepath,
|
||||
PropertyType propertyType)
|
||||
{
|
||||
// if the editor is an upload field, check for additional thumbnail sizes
|
||||
// that can be defined in the prevalue for the property data type. otherwise,
|
||||
// just use the default sizes.
|
||||
var sizes = propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias
|
||||
? ApplicationContext.Current.Services.DataTypeService
|
||||
.GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId)
|
||||
.FirstOrDefault()
|
||||
: string.Empty;
|
||||
|
||||
return GenerateThumbnails(image, filepath, sizes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GenerateResized - Generate at resized filepath derived from origin filepath
|
||||
|
||||
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight)
|
||||
{
|
||||
return GenerateResized(originImage, originFilepath, sizeName, maxWidthHeight, -1, -1);
|
||||
}
|
||||
|
||||
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int fixedWidth, int fixedHeight)
|
||||
{
|
||||
return GenerateResized(originImage, originFilepath, sizeName, -1, fixedWidth, fixedHeight);
|
||||
}
|
||||
|
||||
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight, int fixedWidth, int fixedHeight)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sizeName))
|
||||
sizeName = "UMBRACOSYSTHUMBNAIL";
|
||||
var extension = Path.GetExtension(originFilepath) ?? string.Empty;
|
||||
var filebase = originFilepath.TrimEnd(extension);
|
||||
var resizedFilepath = filebase + "_" + sizeName + extension;
|
||||
|
||||
return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, fixedWidth, fixedHeight);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GenerateResizedAt - Generate at specified resized filepath
|
||||
|
||||
public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight)
|
||||
{
|
||||
return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, -1, -1);
|
||||
}
|
||||
|
||||
public ResizedImage GenerateResizedAt(Image originImage, int fixedWidth, int fixedHeight, string resizedFilepath)
|
||||
{
|
||||
return GenerateResizedAt(originImage, resizedFilepath, -1, fixedWidth, fixedHeight);
|
||||
}
|
||||
|
||||
public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight, int fixedWidth, int fixedHeight)
|
||||
{
|
||||
// target dimensions
|
||||
int width, height;
|
||||
|
||||
// if maxWidthHeight then get ratio
|
||||
if (maxWidthHeight > 0)
|
||||
{
|
||||
var fx = (float)originImage.Size.Width / maxWidthHeight;
|
||||
var fy = (float)originImage.Size.Height / maxWidthHeight;
|
||||
var f = Math.Max(fx, fy); // fit in thumbnail size
|
||||
width = (int)Math.Round(originImage.Size.Width / f);
|
||||
height = (int)Math.Round(originImage.Size.Height / f);
|
||||
if (width == 0) width = 1;
|
||||
if (height == 0) height = 1;
|
||||
}
|
||||
else if (fixedWidth > 0 && fixedHeight > 0)
|
||||
{
|
||||
width = fixedWidth;
|
||||
height = fixedHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = height = 1;
|
||||
}
|
||||
|
||||
// create new image with best quality settings
|
||||
using (var bitmap = new Bitmap(width, height))
|
||||
using (var graphics = Graphics.FromImage(bitmap))
|
||||
{
|
||||
// if the image size is rather large we cannot use the best quality interpolation mode
|
||||
// because we'll get out of mem exceptions. So we detect how big the image is and use
|
||||
// the mid quality interpolation mode when the image size exceeds our max limit.
|
||||
graphics.InterpolationMode = originImage.Width > 5000 || originImage.Height > 5000
|
||||
? InterpolationMode.Bilinear // mid quality
|
||||
: InterpolationMode.HighQualityBicubic; // best quality
|
||||
|
||||
// everything else is best-quality
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
|
||||
// copy the old image to the new and resize
|
||||
var rect = new Rectangle(0, 0, width, height);
|
||||
graphics.DrawImage(originImage, rect, 0, 0, originImage.Width, originImage.Height, GraphicsUnit.Pixel);
|
||||
|
||||
// copy metadata
|
||||
// fixme - er... no?
|
||||
|
||||
// get an encoder - based upon the file type
|
||||
var extension = (Path.GetExtension(resizedFilepath) ?? "").TrimStart('.').ToLowerInvariant();
|
||||
var encoders = ImageCodecInfo.GetImageEncoders();
|
||||
ImageCodecInfo encoder;
|
||||
switch (extension)
|
||||
{
|
||||
case "png":
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/png"));
|
||||
break;
|
||||
case "gif":
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/gif"));
|
||||
break;
|
||||
case "tif":
|
||||
case "tiff":
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/tiff"));
|
||||
break;
|
||||
case "bmp":
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/bmp"));
|
||||
break;
|
||||
// TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the
|
||||
// place so left it here, but it needs to not set a codec if it doesn't know which one to pick
|
||||
// Note: when fixing this: both .jpg and .jpeg should be handled as extensions
|
||||
default:
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/jpeg"));
|
||||
break;
|
||||
}
|
||||
|
||||
// set compresion ratio to 90%
|
||||
var encoderParams = new EncoderParameters();
|
||||
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
|
||||
|
||||
// save the new image
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
bitmap.Save(stream, encoder, encoderParams);
|
||||
stream.Seek(0, 0);
|
||||
if (resizedFilepath.Contains("UMBRACOSYSTHUMBNAIL"))
|
||||
{
|
||||
var filepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToInvariantString());
|
||||
AddFile(filepath, stream);
|
||||
if (extension != "jpg")
|
||||
{
|
||||
filepath = filepath.TrimEnd(extension) + "jpg";
|
||||
stream.Seek(0, 0);
|
||||
AddFile(filepath, stream);
|
||||
}
|
||||
// TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385
|
||||
stream.Seek(0, 0);
|
||||
resizedFilepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", width + "x" + height);
|
||||
}
|
||||
|
||||
AddFile(resizedFilepath, stream);
|
||||
}
|
||||
|
||||
return new ResizedImage(resizedFilepath, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inner classes
|
||||
|
||||
public class ResizedImage
|
||||
{
|
||||
public ResizedImage()
|
||||
{ }
|
||||
|
||||
public ResizedImage(string filepath, int width, int height)
|
||||
{
|
||||
Filepath = filepath;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public string Filepath { get; set; }
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,29 +6,32 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
public class PhysicalFileSystem : IFileSystem
|
||||
public class PhysicalFileSystem : IFileSystem2
|
||||
{
|
||||
// the rooted, filesystem path, using directory separator chars, NOT ending with a separator
|
||||
// eg "c:" or "c:\path\to\site" or "\\server\path"
|
||||
private readonly string _rootPath;
|
||||
|
||||
// the ??? url, using url separator chars, NOT ending with a separator
|
||||
// eg "" (?) or "/Scripts" or ???
|
||||
// _rootPath, but with separators replaced by forward-slashes
|
||||
// eg "c:" or "c:/path/to/site" or "//server/path"
|
||||
// (is used in GetRelativePath)
|
||||
private readonly string _rootPathFwd;
|
||||
|
||||
// the relative url, using url separator chars, NOT ending with a separator
|
||||
// eg "" or "/Views" or "/Media" or "/<vpath>/Media" in case of a virtual path
|
||||
private readonly string _rootUrl;
|
||||
|
||||
// virtualRoot should be "~/path/to/root" eg "~/Views"
|
||||
// the "~/" is mandatory.
|
||||
public PhysicalFileSystem(string virtualRoot)
|
||||
{
|
||||
if (virtualRoot == null) throw new ArgumentNullException("virtualRoot");
|
||||
if (virtualRoot.StartsWith("~/") == false)
|
||||
throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'");
|
||||
|
||||
_rootPath = IOHelper.MapPath(virtualRoot);
|
||||
_rootPath = EnsureDirectorySeparatorChar(_rootPath);
|
||||
_rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
|
||||
_rootUrl = IOHelper.ResolveUrl(virtualRoot);
|
||||
_rootUrl = EnsureUrlSeparatorChar(_rootUrl);
|
||||
_rootUrl = _rootUrl.TrimEnd('/');
|
||||
_rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
|
||||
_rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/');
|
||||
}
|
||||
|
||||
public PhysicalFileSystem(string rootPath, string rootUrl)
|
||||
@@ -43,22 +46,24 @@ namespace Umbraco.Core.IO
|
||||
throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'");
|
||||
|
||||
// rootPath should be... rooted, as in, it's a root path!
|
||||
// but the test suite App.config cannot really "root" anything so we'll have to do it here
|
||||
|
||||
//var localRoot = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var localRoot = IOHelper.GetRootDirectorySafe();
|
||||
if (Path.IsPathRooted(rootPath) == false)
|
||||
{
|
||||
// but the test suite App.config cannot really "root" anything so we have to do it here
|
||||
var localRoot = IOHelper.GetRootDirectorySafe();
|
||||
rootPath = Path.Combine(localRoot, rootPath);
|
||||
}
|
||||
|
||||
rootPath = EnsureDirectorySeparatorChar(rootPath);
|
||||
rootUrl = EnsureUrlSeparatorChar(rootUrl);
|
||||
|
||||
_rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootUrl = rootUrl.TrimEnd('/');
|
||||
_rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
|
||||
_rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets directories in a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the directory.</param>
|
||||
/// <returns>The filesystem-relative path to the directories in the directory.</returns>
|
||||
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
@@ -80,11 +85,20 @@ namespace Umbraco.Core.IO
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
DeleteDirectory(path, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
/// <param name="recursive">A value indicating whether to recursively delete sub-directories.</param>
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
@@ -101,38 +115,71 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a directory exists.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
/// <returns>A value indicating whether a directory exists.</returns>
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
return Directory.Exists(fullPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the file.</param>
|
||||
/// <param name="stream">A stream containing the file data.</param>
|
||||
/// <remarks>Overrides the existing file, if any.</remarks>
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
AddFile(path, stream, true);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
||||
/// <summary>
|
||||
/// Saves a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the file.</param>
|
||||
/// <param name="stream">A stream containing the file data.</param>
|
||||
/// <param name="overrideExisting">A value indicating whether to override the existing file, if any.</param>
|
||||
/// <remarks>If a file exists and <paramref name="overrideExisting"/> is false, an exception is thrown.</remarks>
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
var exists = File.Exists(fullPath);
|
||||
if (exists && overrideIfExists == false)
|
||||
if (exists && overrideExisting == false)
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
if (directory == null) throw new InvalidOperationException("Could not get directory.");
|
||||
Directory.CreateDirectory(directory); // ensure it exists
|
||||
|
||||
if (stream.CanSeek)
|
||||
if (stream.CanSeek) // fixme - what else?
|
||||
stream.Seek(0, 0);
|
||||
|
||||
using (var destination = (Stream)File.Create(fullPath))
|
||||
using (var destination = (Stream) File.Create(fullPath))
|
||||
stream.CopyTo(destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets files in a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
/// <returns>The filesystem-relative path to the files in the directory.</returns>
|
||||
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
return GetFiles(path, "*.*");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets files in a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
/// <param name="filter">A filter.</param>
|
||||
/// <returns>The filesystem-relative path to the matching files in the directory.</returns>
|
||||
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
@@ -154,12 +201,21 @@ namespace Umbraco.Core.IO
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the file.</param>
|
||||
/// <returns></returns>
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
return File.OpenRead(fullPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the file.</param>
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
@@ -176,52 +232,53 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a file exists.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the file.</param>
|
||||
/// <returns>A value indicating whether the file exists.</returns>
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
var fullpath = GetFullPath(path);
|
||||
return File.Exists(fullpath);
|
||||
}
|
||||
|
||||
// beware, many things depend on how the GetRelative/AbsolutePath methods work!
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative path.
|
||||
/// Gets the filesystem-relative path of a full path or of an url.
|
||||
/// </summary>
|
||||
/// <param name="fullPathOrUrl">The full path or url.</param>
|
||||
/// <returns>The path, relative to this filesystem's root.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The relative path is relative to this filesystem's root, not starting with any
|
||||
/// directory separator. If input was recognized as a url (path), then output uses url (path) separator
|
||||
/// chars.</para>
|
||||
/// directory separator. All separators are forward-slashes.</para>
|
||||
/// </remarks>
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
// test url
|
||||
var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char
|
||||
|
||||
if (IOHelper.PathStartsWith(path, _rootUrl, '/')) // if it starts with the root url...
|
||||
return path.Substring(_rootUrl.Length) // strip it
|
||||
.TrimStart('/'); // it's relative
|
||||
// if it starts with the root url, strip it and trim the starting slash to make it relative
|
||||
// eg "/Media/1234/img.jpg" => "1234/img.jpg"
|
||||
if (IOHelper.PathStartsWith(path, _rootUrl, '/'))
|
||||
return path.Substring(_rootUrl.Length).TrimStart('/');
|
||||
|
||||
// test path
|
||||
path = EnsureDirectorySeparatorChar(fullPathOrUrl);
|
||||
// if it starts with the root path, strip it and trim the starting slash to make it relative
|
||||
// eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg"
|
||||
if (IOHelper.PathStartsWith(path, _rootPathFwd, '/'))
|
||||
return path.Substring(_rootPathFwd.Length).TrimStart('/');
|
||||
|
||||
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) // if it starts with the root path
|
||||
return path.Substring(_rootPath.Length) // strip it
|
||||
.TrimStart(Path.DirectorySeparatorChar); // it's relative
|
||||
|
||||
// unchanged - including separators
|
||||
return fullPathOrUrl;
|
||||
// unchanged - what else?
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path.
|
||||
/// </summary>
|
||||
/// <param name="path">The full or relative path.</param>
|
||||
/// <param name="path">The full or filesystem-relative path.</param>
|
||||
/// <returns>The full path.</returns>
|
||||
/// <remarks>
|
||||
/// <para>On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this
|
||||
/// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar.</para>
|
||||
/// filesystem's root) path. All separators are Path.DirectorySeparatorChar.</para>
|
||||
/// </remarks>
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
@@ -229,49 +286,82 @@ namespace Umbraco.Core.IO
|
||||
var opath = path;
|
||||
path = EnsureDirectorySeparatorChar(path);
|
||||
|
||||
// fixme - this part should go!
|
||||
// not sure what we are doing here - so if input starts with a (back) slash,
|
||||
// we assume it's not a FS relative path and we try to convert it... but it
|
||||
// really makes little sense?
|
||||
if (path.StartsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
path = GetRelativePath(path);
|
||||
|
||||
// if already a full path, return
|
||||
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
|
||||
return path;
|
||||
// if not already rooted, combine with the root path
|
||||
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false)
|
||||
path = Path.Combine(_rootPath, path);
|
||||
|
||||
// else combine and sanitize, ie GetFullPath will take care of any relative
|
||||
// sanitize - GetFullPath will take care of any relative
|
||||
// segments in path, eg '../../foo.tmp' - it may throw a SecurityException
|
||||
// if the combined path reaches illegal parts of the filesystem
|
||||
var fpath = Path.Combine(_rootPath, path);
|
||||
fpath = Path.GetFullPath(fpath);
|
||||
path = Path.GetFullPath(path);
|
||||
|
||||
// at that point, path is within legal parts of the filesystem, ie we have
|
||||
// permissions to reach that path, but it may nevertheless be outside of
|
||||
// our root path, due to relative segments, so better check
|
||||
if (IOHelper.PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar))
|
||||
return fpath;
|
||||
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
|
||||
return path;
|
||||
|
||||
// nothing prevents us to reach the file, security-wise, yet it is outside
|
||||
// this filesystem's root - throw
|
||||
throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path.</param>
|
||||
/// <returns>The url.</returns>
|
||||
/// <remarks>All separators are forward-slashes.</remarks>
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
path = EnsureUrlSeparatorChar(path).Trim('/');
|
||||
return _rootUrl + "/" + path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last-modified date of a directory or file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the directory or the file.</param>
|
||||
/// <returns>The last modified date of the directory or the file.</returns>
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
return DirectoryExists(path)
|
||||
? new DirectoryInfo(GetFullPath(path)).LastWriteTimeUtc
|
||||
: new FileInfo(GetFullPath(path)).LastWriteTimeUtc;
|
||||
var fullpath = GetFullPath(path);
|
||||
return DirectoryExists(fullpath)
|
||||
? new DirectoryInfo(fullpath).LastWriteTimeUtc
|
||||
: new FileInfo(fullpath).LastWriteTimeUtc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the created date of a directory or file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the directory or the file.</param>
|
||||
/// <returns>The created date of the directory or the file.</returns>
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
return DirectoryExists(path)
|
||||
? Directory.GetCreationTimeUtc(GetFullPath(path))
|
||||
: File.GetCreationTimeUtc(GetFullPath(path));
|
||||
var fullpath = GetFullPath(path);
|
||||
return DirectoryExists(fullpath)
|
||||
? Directory.GetCreationTimeUtc(fullpath)
|
||||
: File.GetCreationTimeUtc(fullpath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the file.</param>
|
||||
/// <returns>The file of the size, in bytes.</returns>
|
||||
/// <remarks>If the file does not exist, returns -1.</remarks>
|
||||
public long GetSize(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
var file = new FileInfo(fullPath);
|
||||
return file.Exists ? file.Length : -1;
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ResizedImage
|
||||
{
|
||||
public ResizedImage()
|
||||
{
|
||||
}
|
||||
|
||||
public ResizedImage(int width, int height, string fileName)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
FileName = fileName;
|
||||
}
|
||||
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public string FileName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowFileSystem : IFileSystem2
|
||||
{
|
||||
private readonly IFileSystem _fs;
|
||||
private readonly IFileSystem2 _sfs;
|
||||
|
||||
public ShadowFileSystem(IFileSystem fs, IFileSystem2 sfs)
|
||||
{
|
||||
_fs = fs;
|
||||
_sfs = sfs;
|
||||
}
|
||||
|
||||
public IFileSystem Inner
|
||||
{
|
||||
get { return _fs; }
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
if (_nodes == null) return;
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var kvp in _nodes)
|
||||
{
|
||||
if (kvp.Value.IsExist)
|
||||
{
|
||||
if (kvp.Value.IsFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = _sfs.OpenFile(kvp.Key))
|
||||
_fs.AddFile(kvp.Key, stream, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptions.Add(new Exception("Could not save file \"" + kvp.Key + "\".", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (kvp.Value.IsDir)
|
||||
_fs.DeleteDirectory(kvp.Key, true);
|
||||
else
|
||||
_fs.DeleteFile(kvp.Key);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptions.Add(new Exception("Could not delete " + (kvp.Value.IsDir ? "directory": "file") + " \"" + kvp.Key + "\".", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
_nodes.Clear();
|
||||
|
||||
if (exceptions.Count == 0) return;
|
||||
throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
|
||||
}
|
||||
|
||||
private Dictionary<string, ShadowNode> _nodes;
|
||||
|
||||
private Dictionary<string, ShadowNode> Nodes { get { return _nodes ?? (_nodes = new Dictionary<string, ShadowNode>()); } }
|
||||
|
||||
private class ShadowNode
|
||||
{
|
||||
public ShadowNode(bool isDelete, bool isdir)
|
||||
{
|
||||
IsDelete = isDelete;
|
||||
IsDir = isdir;
|
||||
}
|
||||
|
||||
public bool IsDelete { get; private set; }
|
||||
public bool IsDir { get; private set; }
|
||||
|
||||
public bool IsExist { get { return IsDelete == false; } }
|
||||
public bool IsFile { get { return IsDir == false; } }
|
||||
}
|
||||
|
||||
private static string NormPath(string path)
|
||||
{
|
||||
return path.ToLowerInvariant().Replace("\\", "/");
|
||||
}
|
||||
|
||||
// values can be "" (root), "foo", "foo/bar"...
|
||||
private static bool IsChild(string path, string input)
|
||||
{
|
||||
if (input.StartsWith(path) == false || input.Length < path.Length + 2)
|
||||
return false;
|
||||
if (path.Length > 0 && input[path.Length] != '/') return false;
|
||||
var pos = input.IndexOf("/", path.Length + 1, StringComparison.OrdinalIgnoreCase);
|
||||
return pos < 0;
|
||||
}
|
||||
|
||||
private static bool IsDescendant(string path, string input)
|
||||
{
|
||||
if (input.StartsWith(path) == false || input.Length < path.Length + 2)
|
||||
return false;
|
||||
return path.Length == 0 || input[path.Length] == '/';
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
var normPath = NormPath(path);
|
||||
var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray();
|
||||
var directories = _fs.GetDirectories(path);
|
||||
return directories
|
||||
.Except(shadows.Where(kvp => (kvp.Value.IsDir && kvp.Value.IsDelete) || (kvp.Value.IsFile && kvp.Value.IsExist))
|
||||
.Select(kvp => kvp.Key))
|
||||
.Union(shadows.Where(kvp => kvp.Value.IsDir && kvp.Value.IsExist).Select(kvp => kvp.Key))
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
DeleteDirectory(path, false);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
if (DirectoryExists(path) == false) return;
|
||||
var normPath = NormPath(path);
|
||||
if (recursive)
|
||||
{
|
||||
Nodes[normPath] = new ShadowNode(true, true);
|
||||
var remove = Nodes.Where(x => IsDescendant(normPath, x.Key)).ToList();
|
||||
foreach (var kvp in remove) Nodes.Remove(kvp.Key);
|
||||
Delete(path, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Nodes.Any(x => IsChild(normPath, x.Key) && x.Value.IsExist) // shadow content
|
||||
|| _fs.GetDirectories(path).Any() || _fs.GetFiles(path).Any()) // actual content
|
||||
throw new InvalidOperationException("Directory is not empty.");
|
||||
Nodes[path] = new ShadowNode(true, true);
|
||||
var remove = Nodes.Where(x => IsChild(normPath, x.Key)).ToList();
|
||||
foreach (var kvp in remove) Nodes.Remove(kvp.Key);
|
||||
Delete(path, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Delete(string path, bool recurse)
|
||||
{
|
||||
foreach (var file in _fs.GetFiles(path))
|
||||
{
|
||||
Nodes[NormPath(file)] = new ShadowNode(true, false);
|
||||
}
|
||||
foreach (var dir in _fs.GetDirectories(path))
|
||||
{
|
||||
Nodes[NormPath(dir)] = new ShadowNode(true, true);
|
||||
if (recurse) Delete(dir, true);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf))
|
||||
return sf.IsDir && sf.IsExist;
|
||||
return _fs.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
AddFile(path, stream, true);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
||||
{
|
||||
ShadowNode sf;
|
||||
var normPath = NormPath(path);
|
||||
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
var parts = normPath.Split('/');
|
||||
for (var i = 0; i < parts.Length - 1; i++)
|
||||
{
|
||||
var dirPath = string.Join("/", parts.Take(i + 1));
|
||||
ShadowNode sd;
|
||||
if (Nodes.TryGetValue(dirPath, out sd))
|
||||
{
|
||||
if (sd.IsFile) throw new InvalidOperationException("Invalid path.");
|
||||
if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_fs.DirectoryExists(dirPath)) continue;
|
||||
if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path.");
|
||||
Nodes[dirPath] = new ShadowNode(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
_sfs.AddFile(path, stream, overrideIfExists);
|
||||
Nodes[normPath] = new ShadowNode(false, false);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
var normPath = NormPath(path);
|
||||
var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray();
|
||||
var files = _fs.GetFiles(path);
|
||||
return files
|
||||
.Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir)
|
||||
.Select(kvp => kvp.Key))
|
||||
.Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist).Select(kvp => kvp.Key))
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
return _fs.GetFiles(path, filter);
|
||||
}
|
||||
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf))
|
||||
return sf.IsDir || sf.IsDelete ? null : _sfs.OpenFile(path);
|
||||
return _fs.OpenFile(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
if (FileExists(path) == false) return;
|
||||
Nodes[NormPath(path)] = new ShadowNode(true, false);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf))
|
||||
return sf.IsFile && sf.IsExist;
|
||||
return _fs.FileExists(path);
|
||||
}
|
||||
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
return _fs.GetRelativePath(fullPathOrUrl);
|
||||
}
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return _fs.GetFullPath(path);
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return _fs.GetUrl(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf) == false) return _fs.GetLastModified(path);
|
||||
if (sf.IsDelete) throw new InvalidOperationException("Invalid path.");
|
||||
return _sfs.GetLastModified(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf) == false) return _fs.GetCreated(path);
|
||||
if (sf.IsDelete) throw new InvalidOperationException("Invalid path.");
|
||||
return _sfs.GetCreated(path);
|
||||
}
|
||||
|
||||
public long GetSize(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf) == false)
|
||||
{
|
||||
// the inner filesystem (_fs) can be IFileSystem2... or just IFileSystem
|
||||
// figure it out and use the most effective GetSize method
|
||||
var fs2 = _fs as IFileSystem2;
|
||||
return fs2 == null ? _fs.GetSize(path) : fs2.GetSize(path);
|
||||
}
|
||||
if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path.");
|
||||
return _sfs.GetSize(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowFileSystemsScope : ICompletable
|
||||
{
|
||||
// note: taking a reference to the _manager instead of using manager.Current
|
||||
// to avoid using Current everywhere but really, we support only 1 scope at
|
||||
// a time, not multiple scopes in case of multiple managers (not supported)
|
||||
|
||||
private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystemsScope";
|
||||
private static readonly object Locker = new object();
|
||||
private readonly Guid _id;
|
||||
private readonly ShadowWrapper[] _wrappers;
|
||||
|
||||
static ShadowFileSystemsScope()
|
||||
{
|
||||
SafeCallContext.Register(
|
||||
() =>
|
||||
{
|
||||
var scope = CallContext.LogicalGetData(ItemKey);
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
return scope;
|
||||
},
|
||||
o =>
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException();
|
||||
if (o != null) CallContext.LogicalSetData(ItemKey, o);
|
||||
});
|
||||
}
|
||||
|
||||
private ShadowFileSystemsScope(Guid id, ShadowWrapper[] wrappers)
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("Shadow " + id + ".");
|
||||
_id = id;
|
||||
_wrappers = wrappers;
|
||||
foreach (var wrapper in _wrappers)
|
||||
wrapper.Shadow(id);
|
||||
}
|
||||
|
||||
// internal for tests + FileSystemProviderManager
|
||||
// do NOT use otherwise
|
||||
internal static ShadowFileSystemsScope CreateScope(Guid id, ShadowWrapper[] wrappers)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing.");
|
||||
CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter
|
||||
}
|
||||
return new ShadowFileSystemsScope(id, wrappers);
|
||||
}
|
||||
|
||||
internal static bool InScope
|
||||
{
|
||||
get { return NoScope == false; }
|
||||
}
|
||||
|
||||
internal static bool NoScope
|
||||
{
|
||||
get { return CallContext.LogicalGetData(ItemKey) == null; }
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (complete).");
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var wrapper in _wrappers)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
wrapper.UnShadow(true);
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
exceptions.Add(ae);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
|
||||
|
||||
// last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can)
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (abort)");
|
||||
foreach (var wrapper in _wrappers)
|
||||
wrapper.UnShadow(false); // should not throw
|
||||
}
|
||||
finally
|
||||
{
|
||||
// last, & always
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowWrapper : IFileSystem2
|
||||
{
|
||||
private readonly IFileSystem _innerFileSystem;
|
||||
private readonly string _shadowPath;
|
||||
private ShadowFileSystem _shadowFileSystem;
|
||||
private string _shadowDir;
|
||||
|
||||
public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath)
|
||||
{
|
||||
_innerFileSystem = innerFileSystem;
|
||||
_shadowPath = shadowPath;
|
||||
}
|
||||
|
||||
internal void Shadow(Guid id)
|
||||
{
|
||||
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
|
||||
// on ShadowFileSystemsScope.None - and if None is false then we should be running
|
||||
// in a single thread anyways
|
||||
|
||||
var virt = "~/App_Data/Shadow/" + id + "/" + _shadowPath;
|
||||
_shadowDir = IOHelper.MapPath(virt);
|
||||
Directory.CreateDirectory(_shadowDir);
|
||||
var tempfs = new PhysicalFileSystem(virt);
|
||||
_shadowFileSystem = new ShadowFileSystem(_innerFileSystem, tempfs);
|
||||
}
|
||||
|
||||
internal void UnShadow(bool complete)
|
||||
{
|
||||
var shadowFileSystem = _shadowFileSystem;
|
||||
var dir = _shadowDir;
|
||||
_shadowFileSystem = null;
|
||||
_shadowDir = null;
|
||||
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
if (complete) shadowFileSystem.Complete();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// in any case, cleanup
|
||||
try
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
dir = dir.Substring(0, dir.Length - _shadowPath.Length - 1);
|
||||
if (Directory.EnumerateFileSystemEntries(dir).Any() == false)
|
||||
Directory.Delete(dir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ugly, isn't it? but if we cannot cleanup, bah, just leave it there
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IFileSystem FileSystem
|
||||
{
|
||||
get { return ShadowFileSystemsScope.NoScope ? _innerFileSystem : _shadowFileSystem; }
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
return FileSystem.GetDirectories(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
FileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
FileSystem.DeleteDirectory(path, recursive);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return FileSystem.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
FileSystem.AddFile(path, stream);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting)
|
||||
{
|
||||
FileSystem.AddFile(path, stream, overrideExisting);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
return FileSystem.GetFiles(path);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
return FileSystem.GetFiles(path, filter);
|
||||
}
|
||||
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
return FileSystem.OpenFile(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
FileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return FileSystem.FileExists(path);
|
||||
}
|
||||
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
return FileSystem.GetRelativePath(fullPathOrUrl);
|
||||
}
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return FileSystem.GetFullPath(path);
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return FileSystem.GetUrl(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
return FileSystem.GetLastModified(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
return FileSystem.GetCreated(path);
|
||||
}
|
||||
|
||||
public long GetSize(string path)
|
||||
{
|
||||
var filesystem = FileSystem; // will be either a ShadowFileSystem OR the actual underlying IFileSystem
|
||||
|
||||
// and the underlying filesystem can be IFileSystem2... or just IFileSystem
|
||||
// figure it out and use the most effective GetSize method
|
||||
var filesystem2 = filesystem as IFileSystem2;
|
||||
return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,23 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string PartialViews
|
||||
{
|
||||
get
|
||||
{
|
||||
return MvcViews + "/Partials/";
|
||||
}
|
||||
}
|
||||
|
||||
public static string MacroPartials
|
||||
{
|
||||
get
|
||||
{
|
||||
return MvcViews + "/MacroPartials/";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static string Media
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Media;
|
||||
|
||||
@@ -86,15 +80,15 @@ namespace Umbraco.Core.IO
|
||||
private void Initialize()
|
||||
{
|
||||
Filename = _fs.GetFileName(Path);
|
||||
Extension = string.IsNullOrEmpty(_fs.GetExtension(Path)) == false
|
||||
? _fs.GetExtension(Path).Substring(1).ToLowerInvariant()
|
||||
var ext = _fs.GetExtension(Path);
|
||||
Extension = string.IsNullOrEmpty(ext) == false
|
||||
? ext.TrimStart('.').ToLowerInvariant()
|
||||
: "";
|
||||
Url = _fs.GetUrl(Path);
|
||||
|
||||
Exists = _fs.FileExists(Path);
|
||||
if (Exists == false)
|
||||
{
|
||||
LogHelper.Warn<UmbracoMediaFile>("The media file doesn't exist: " + Path);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Exists { get; private set; }
|
||||
@@ -117,27 +111,15 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_length == null)
|
||||
{
|
||||
if (Exists)
|
||||
{
|
||||
_length = _fs.GetSize(Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_length = -1;
|
||||
}
|
||||
}
|
||||
if (_length.HasValue) return _length.Value;
|
||||
_length = Exists ? _fs.GetSize(Path) : -1;
|
||||
return _length.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsResizing
|
||||
{
|
||||
get
|
||||
{
|
||||
return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(Extension);
|
||||
}
|
||||
get { return _fs.IsImageFile(Extension); }
|
||||
}
|
||||
|
||||
public string GetFriendlyName()
|
||||
@@ -147,67 +129,49 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public Size GetDimensions()
|
||||
{
|
||||
if (_size == null)
|
||||
{
|
||||
if (_fs.FileExists(Path))
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
if (_size.HasValue) return _size.Value;
|
||||
|
||||
using (var fs = _fs.OpenFile(Path))
|
||||
{
|
||||
_size = ImageHelper.GetDimensions(fs);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (_fs.FileExists(Path))
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
|
||||
using (var fs = _fs.OpenFile(Path))
|
||||
{
|
||||
_size = new Size(-1, -1);
|
||||
_size = _fs.GetDimensions(fs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_size = new Size(-1, -1);
|
||||
}
|
||||
|
||||
return _size.Value;
|
||||
}
|
||||
|
||||
public string Resize(int width, int height)
|
||||
{
|
||||
if (Exists)
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
if (Exists == false) return string.Empty;
|
||||
|
||||
var fileNameThumb = DoResize(width, height, -1, string.Empty);
|
||||
|
||||
return _fs.GetUrl(fileNameThumb);
|
||||
}
|
||||
return string.Empty;
|
||||
EnsureFileSupportsResizing();
|
||||
var filepath = Resize(width, height, -1, string.Empty);
|
||||
return _fs.GetUrl(filepath);
|
||||
}
|
||||
|
||||
public string Resize(int maxWidthHeight, string fileNameAddition)
|
||||
{
|
||||
if (Exists)
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
if (Exists == false) return string.Empty;
|
||||
|
||||
var fileNameThumb = DoResize(-1, -1, maxWidthHeight, fileNameAddition);
|
||||
|
||||
return _fs.GetUrl(fileNameThumb);
|
||||
}
|
||||
return string.Empty;
|
||||
EnsureFileSupportsResizing();
|
||||
var filepath = Resize(-1, -1, maxWidthHeight, fileNameAddition);
|
||||
return _fs.GetUrl(filepath);
|
||||
}
|
||||
|
||||
private string DoResize(int width, int height, int maxWidthHeight, string fileNameAddition)
|
||||
private string Resize(int width, int height, int maxWidthHeight, string sizeName)
|
||||
{
|
||||
using (var fs = _fs.OpenFile(Path))
|
||||
using (var filestream = _fs.OpenFile(Path))
|
||||
using (var image = Image.FromStream(filestream))
|
||||
{
|
||||
using (var image = Image.FromStream(fs))
|
||||
{
|
||||
var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition)
|
||||
? string.Format("{0}_UMBRACOSYSTHUMBNAIL." + Extension, Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)))
|
||||
: string.Format("{0}_{1}." + Extension, Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition);
|
||||
|
||||
var thumbnail = maxWidthHeight == -1
|
||||
? ImageHelper.GenerateThumbnail(image, width, height, fileNameThumb, Extension, _fs)
|
||||
: ImageHelper.GenerateThumbnail(image, maxWidthHeight, fileNameThumb, Extension, _fs);
|
||||
|
||||
return thumbnail.FileName;
|
||||
}
|
||||
return _fs.GenerateResized(image, Path, sizeName, maxWidthHeight, width, height).Filepath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +180,5 @@ namespace Umbraco.Core.IO
|
||||
if (SupportsResizing == false)
|
||||
throw new InvalidOperationException(string.Format("The file {0} is not an image, so can't get dimensions", Filename));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
internal class DebugDiagnosticsLogger : ILogger
|
||||
/// <summary>
|
||||
/// Implements <see cref="ILogger"/> on top of System.Diagnostics.Debug.
|
||||
/// </summary>
|
||||
/// <remarks>Useful for tests.</remarks>
|
||||
public class DebugDiagnosticsLogger : ILogger
|
||||
{
|
||||
public void Error(Type callingType, string message, Exception exception)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
public static class ImageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the MIME type of an image.
|
||||
/// </summary>
|
||||
/// <param name="image">The image.</param>
|
||||
/// <returns>The MIME type of the image.</returns>
|
||||
public static string GetMimeType(this Image image)
|
||||
{
|
||||
var format = image.RawFormat;
|
||||
var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
|
||||
return codec.MimeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Media.Exif;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class used for imaging
|
||||
/// </summary>
|
||||
internal static class ImageHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the dimensions of an image based on a stream
|
||||
/// </summary>
|
||||
/// <param name="imageStream"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// First try with EXIF, this is because it is insanely faster and doesn't use any memory to read exif data than to load in the entire
|
||||
/// image via GDI. Otherwise loading an image into GDI consumes a crazy amount of memory on large images.
|
||||
///
|
||||
/// Of course EXIF data might not exist in every file and can only exist in JPGs
|
||||
/// </remarks>
|
||||
public static Size GetDimensions(Stream imageStream)
|
||||
{
|
||||
//Try to load with exif
|
||||
try
|
||||
{
|
||||
var jpgInfo = ImageFile.FromStream(imageStream);
|
||||
|
||||
if (jpgInfo.Format != ImageFileFormat.Unknown
|
||||
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension)
|
||||
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension))
|
||||
{
|
||||
var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value);
|
||||
var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value);
|
||||
if (height > 0 && width > 0)
|
||||
{
|
||||
return new Size(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//We will just swallow, just means we can't read exif data, we don't want to log an error either
|
||||
}
|
||||
|
||||
//we have no choice but to try to read in via GDI
|
||||
using (var image = Image.FromStream(imageStream))
|
||||
{
|
||||
|
||||
var fileWidth = image.Width;
|
||||
var fileHeight = image.Height;
|
||||
return new Size(fileWidth, fileHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static string GetMimeType(this Image image)
|
||||
{
|
||||
var format = image.RawFormat;
|
||||
var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
|
||||
return codec.MimeType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the thumbnails if the image is larger than all of the specified ones.
|
||||
/// </summary>
|
||||
/// <param name="fs"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="extension"></param>
|
||||
/// <param name="originalImage"></param>
|
||||
/// <param name="additionalThumbSizes"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<ResizedImage> GenerateMediaThumbnails(
|
||||
IFileSystem fs,
|
||||
string fileName,
|
||||
string extension,
|
||||
Image originalImage,
|
||||
IEnumerable<int> additionalThumbSizes)
|
||||
{
|
||||
|
||||
var result = new List<ResizedImage>();
|
||||
|
||||
var allSizesDictionary = new Dictionary<int, string> { { 100, "thumb" }, { 500, "big-thumb" } };
|
||||
|
||||
//combine the static dictionary with the additional sizes with only unique values
|
||||
var allSizes = allSizesDictionary.Select(kv => kv.Key)
|
||||
.Union(additionalThumbSizes.Where(x => x > 0).Distinct());
|
||||
|
||||
var sizesDictionary = allSizes.ToDictionary(s => s, s => allSizesDictionary.ContainsKey(s) ? allSizesDictionary[s] : "");
|
||||
|
||||
foreach (var s in sizesDictionary)
|
||||
{
|
||||
var size = s.Key;
|
||||
var name = s.Value;
|
||||
if (originalImage.Width >= size && originalImage.Height >= size)
|
||||
{
|
||||
result.Add(Resize(fs, fileName, extension, size, name, originalImage));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an image resize
|
||||
/// </summary>
|
||||
/// <param name="fileSystem"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="extension"></param>
|
||||
/// <param name="maxWidthHeight"></param>
|
||||
/// <param name="fileNameAddition"></param>
|
||||
/// <param name="originalImage"></param>
|
||||
/// <returns></returns>
|
||||
private static ResizedImage Resize(IFileSystem fileSystem, string path, string extension, int maxWidthHeight, string fileNameAddition, Image originalImage)
|
||||
{
|
||||
var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition)
|
||||
? string.Format("{0}_UMBRACOSYSTHUMBNAIL." + extension, path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal)))
|
||||
: string.Format("{0}_{1}." + extension, path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition);
|
||||
|
||||
var thumb = GenerateThumbnail(
|
||||
originalImage,
|
||||
maxWidthHeight,
|
||||
fileNameThumb,
|
||||
extension,
|
||||
fileSystem);
|
||||
|
||||
return thumb;
|
||||
}
|
||||
|
||||
internal static ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, string thumbnailFileName, string extension, IFileSystem fs)
|
||||
{
|
||||
return GenerateThumbnail(image, maxWidthHeight, -1, -1, thumbnailFileName, extension, fs);
|
||||
}
|
||||
|
||||
internal static ResizedImage GenerateThumbnail(Image image, int fixedWidth, int fixedHeight, string thumbnailFileName, string extension, IFileSystem fs)
|
||||
{
|
||||
return GenerateThumbnail(image, -1, fixedWidth, fixedHeight, thumbnailFileName, extension, fs);
|
||||
}
|
||||
|
||||
private static ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, int fixedWidth, int fixedHeight, string thumbnailFileName, string extension, IFileSystem fs)
|
||||
{
|
||||
// Generate thumbnail
|
||||
float f = 1;
|
||||
if (maxWidthHeight >= 0)
|
||||
{
|
||||
var fx = (float)image.Size.Width / maxWidthHeight;
|
||||
var fy = (float)image.Size.Height / maxWidthHeight;
|
||||
|
||||
// must fit in thumbnail size
|
||||
f = Math.Max(fx, fy);
|
||||
}
|
||||
|
||||
//depending on if we are doing fixed width resizing or not.
|
||||
fixedWidth = (maxWidthHeight > 0) ? image.Width : fixedWidth;
|
||||
fixedHeight = (maxWidthHeight > 0) ? image.Height : fixedHeight;
|
||||
|
||||
var widthTh = (int)Math.Round(fixedWidth / f);
|
||||
var heightTh = (int)Math.Round(fixedHeight / f);
|
||||
|
||||
// fixes for empty width or height
|
||||
if (widthTh == 0)
|
||||
widthTh = 1;
|
||||
if (heightTh == 0)
|
||||
heightTh = 1;
|
||||
|
||||
// Create new image with best quality settings
|
||||
using (var bp = new Bitmap(widthTh, heightTh))
|
||||
{
|
||||
using (var g = Graphics.FromImage(bp))
|
||||
{
|
||||
//if the image size is rather large we cannot use the best quality interpolation mode
|
||||
// because we'll get out of mem exceptions. So we'll detect how big the image is and use
|
||||
// the mid quality interpolation mode when the image size exceeds our max limit.
|
||||
|
||||
if (image.Width > 5000 || image.Height > 5000)
|
||||
{
|
||||
//use mid quality
|
||||
g.InterpolationMode = InterpolationMode.Bilinear;
|
||||
}
|
||||
else
|
||||
{
|
||||
//use best quality
|
||||
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
}
|
||||
|
||||
|
||||
g.SmoothingMode = SmoothingMode.HighQuality;
|
||||
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
g.CompositingQuality = CompositingQuality.HighQuality;
|
||||
|
||||
// Copy the old image to the new and resized
|
||||
var rect = new Rectangle(0, 0, widthTh, heightTh);
|
||||
g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
|
||||
|
||||
// Copy metadata
|
||||
var imageEncoders = ImageCodecInfo.GetImageEncoders();
|
||||
ImageCodecInfo codec;
|
||||
switch (extension.ToLower())
|
||||
{
|
||||
case "png":
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/png"));
|
||||
break;
|
||||
case "gif":
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/gif"));
|
||||
break;
|
||||
case "tif":
|
||||
case "tiff":
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/tiff"));
|
||||
break;
|
||||
case "bmp":
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/bmp"));
|
||||
break;
|
||||
// TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the
|
||||
// place so left it here, but it needs to not set a codec if it doesn't know which one to pick
|
||||
// Note: when fixing this: both .jpg and .jpeg should be handled as extensions
|
||||
default:
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/jpeg"));
|
||||
break;
|
||||
}
|
||||
|
||||
// Set compresion ratio to 90%
|
||||
var ep = new EncoderParameters();
|
||||
ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
|
||||
|
||||
// Save the new image using the dimensions of the image
|
||||
var predictableThumbnailName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToString(CultureInfo.InvariantCulture));
|
||||
var predictableThumbnailNameJpg = predictableThumbnailName.Substring(0, predictableThumbnailName.LastIndexOf(".", StringComparison.Ordinal)) + ".jpg";
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
bp.Save(ms, codec, ep);
|
||||
ms.Seek(0, 0);
|
||||
|
||||
fs.AddFile(predictableThumbnailName, ms);
|
||||
fs.AddFile(predictableThumbnailNameJpg, ms);
|
||||
}
|
||||
|
||||
// TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385
|
||||
var newFileName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", string.Format("{0}x{1}", widthTh, heightTh));
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
bp.Save(ms, codec, ep);
|
||||
ms.Seek(0, 0);
|
||||
|
||||
fs.AddFile(newFileName, ms);
|
||||
}
|
||||
|
||||
return new ResizedImage(widthTh, heightTh, newFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal singleton to handle the numbering of subfolders within the Media-folder.
|
||||
/// When this class is initiated it will look for numbered subfolders and select the highest number,
|
||||
/// which will be the start point for the naming of the next subfolders. If no subfolders exists
|
||||
/// then the starting point will be 1000, ie. /media/1000/koala.jpg
|
||||
/// </summary>
|
||||
internal class MediaSubfolderCounter
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private long _numberedFolder = 1000;//Default starting point
|
||||
private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim();
|
||||
private static readonly Lazy<MediaSubfolderCounter> Lazy = new Lazy<MediaSubfolderCounter>(() => new MediaSubfolderCounter());
|
||||
|
||||
public static MediaSubfolderCounter Current { get { return Lazy.Value; } }
|
||||
|
||||
private MediaSubfolderCounter()
|
||||
{
|
||||
var folders = new List<long>();
|
||||
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
|
||||
var directories = fs.GetDirectories("");
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
long dirNum;
|
||||
if (long.TryParse(directory, out dirNum))
|
||||
{
|
||||
folders.Add(dirNum);
|
||||
}
|
||||
}
|
||||
var last = folders.OrderBy(x => x).LastOrDefault();
|
||||
if(last != default(long))
|
||||
_numberedFolder = last;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns an increment of the numbered media subfolders.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="System.Int64"/> value</returns>
|
||||
public long Increment()
|
||||
{
|
||||
using (new ReadLock(ClearLock))
|
||||
{
|
||||
_numberedFolder = _numberedFolder + 1;
|
||||
return _numberedFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to manage auto-fill properties for upload fields.
|
||||
/// </summary>
|
||||
internal class UploadAutoFillProperties
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly MediaFileSystem _mediaFileSystem;
|
||||
private readonly IContentSection _contentSettings;
|
||||
|
||||
public UploadAutoFillProperties(MediaFileSystem mediaFileSystem, ILogger logger, IContentSection contentSettings)
|
||||
{
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_logger = logger;
|
||||
_contentSettings = contentSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the auto-fill configuration for a specified property alias.
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <returns>The auto-fill configuration for the specified property alias, or null.</returns>
|
||||
public IImagingAutoFillUploadField GetConfig(string propertyTypeAlias)
|
||||
{
|
||||
var autoFillConfigs = _contentSettings.ImageAutoFillProperties;
|
||||
return autoFillConfigs == null ? null : autoFillConfigs.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the auto-fill properties of a content item, for a specified property alias.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
public void Reset(IContentBase content, string propertyTypeAlias)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
|
||||
|
||||
// get the config, no config = nothing to do
|
||||
var autoFillConfig = GetConfig(propertyTypeAlias);
|
||||
if (autoFillConfig == null) return; // nothing
|
||||
|
||||
// reset
|
||||
Reset(content, autoFillConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the auto-fill properties of a content item, for a specified auto-fill configuration.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="autoFillConfig">The auto-fill configuration.</param>
|
||||
public void Reset(IContentBase content, IImagingAutoFillUploadField autoFillConfig)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
ResetProperties(content, autoFillConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the auto-fill properties of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
|
||||
public void Populate(IContentBase content, string propertyTypeAlias, string filepath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
|
||||
|
||||
// no property = nothing to do
|
||||
if (content.Properties.Contains(propertyTypeAlias) == false) return;
|
||||
|
||||
// get the config, no config = nothing to do
|
||||
var autoFillConfig = GetConfig(propertyTypeAlias);
|
||||
if (autoFillConfig == null) return; // nothing
|
||||
|
||||
// populate
|
||||
Populate(content, autoFillConfig, filepath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the auto-fill properties of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
|
||||
/// <param name="filestream">The stream containing the file data.</param>
|
||||
/// <param name="image">The file data as an image object.</param>
|
||||
public void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream, Image image = null)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
|
||||
|
||||
// no property = nothing to do
|
||||
if (content.Properties.Contains(propertyTypeAlias) == false) return;
|
||||
|
||||
// get the config, no config = nothing to do
|
||||
var autoFillConfig = GetConfig(propertyTypeAlias);
|
||||
if (autoFillConfig == null) return; // nothing
|
||||
|
||||
// populate
|
||||
Populate(content, autoFillConfig, filepath, filestream, image);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the auto-fill properties of a content item, for a specified auto-fill configuration.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="autoFillConfig">The auto-fill configuration.</param>
|
||||
/// <param name="filepath">The filesystem path to the uploaded file.</param>
|
||||
/// <remarks>The <paramref name="filepath"/> parameter is the path relative to the filesystem.</remarks>
|
||||
public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
// no file = reset, file = auto-fill
|
||||
if (filepath.IsNullOrWhiteSpace())
|
||||
{
|
||||
ResetProperties(content, autoFillConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if anything goes wrong, just reset the properties
|
||||
try
|
||||
{
|
||||
using (var filestream = _mediaFileSystem.OpenFile(filepath))
|
||||
{
|
||||
var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.');
|
||||
var size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null;
|
||||
SetProperties(content, autoFillConfig, size, filestream.Length, extension);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(typeof(UploadAutoFillProperties), "Could not populate upload auto-fill properties for file \""
|
||||
+ filepath + "\".", ex);
|
||||
ResetProperties(content, autoFillConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the auto-fill properties of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="autoFillConfig"></param>
|
||||
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
|
||||
/// <param name="filestream">The stream containing the file data.</param>
|
||||
/// <param name="image">The file data as an image object.</param>
|
||||
public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, Image image = null)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
// no file = reset, file = auto-fill
|
||||
if (filepath.IsNullOrWhiteSpace() || filestream == null)
|
||||
{
|
||||
ResetProperties(content, autoFillConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.');
|
||||
Size? size;
|
||||
if (image == null)
|
||||
size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null;
|
||||
else
|
||||
size = new Size(image.Width, image.Height);
|
||||
SetProperties(content, autoFillConfig, size, filestream.Length, extension);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, Size? size, long length, string extension)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.WidthFieldAlias))
|
||||
content.Properties[autoFillConfig.WidthFieldAlias].Value = size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.HeightFieldAlias))
|
||||
content.Properties[autoFillConfig.HeightFieldAlias].Value = size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.LengthFieldAlias))
|
||||
content.Properties[autoFillConfig.LengthFieldAlias].Value = length;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias))
|
||||
content.Properties[autoFillConfig.ExtensionFieldAlias].Value = extension;
|
||||
}
|
||||
|
||||
private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.WidthFieldAlias))
|
||||
content.Properties[autoFillConfig.WidthFieldAlias].Value = string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.HeightFieldAlias))
|
||||
content.Properties[autoFillConfig.HeightFieldAlias].Value = string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.LengthFieldAlias))
|
||||
content.Properties[autoFillConfig.LengthFieldAlias].Value = string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias))
|
||||
content.Properties[autoFillConfig.ExtensionFieldAlias].Value = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -329,7 +319,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
if (property.Value is string)
|
||||
{
|
||||
var value = (string)property.Value;
|
||||
var value = (string) property.Value;
|
||||
property.Value = value.ToValidXmlString();
|
||||
}
|
||||
}
|
||||
@@ -444,154 +434,116 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
}
|
||||
|
||||
public static IContentTypeComposition GetContentType(this IContentBase contentBase)
|
||||
{
|
||||
if (contentBase == null) throw new ArgumentNullException("contentBase");
|
||||
|
||||
var content = contentBase as IContent;
|
||||
if (content != null) return content.ContentType;
|
||||
var media = contentBase as IMedia;
|
||||
if (media != null) return media.ContentType;
|
||||
var member = contentBase as IMember;
|
||||
if (member != null) return member.ContentType;
|
||||
throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + ".");
|
||||
}
|
||||
|
||||
#region SetValue for setting file contents
|
||||
|
||||
/// <summary>
|
||||
/// Sets and uploads the file from a HttpPostedFileBase object as the property value
|
||||
/// Stores and sets an uploaded HttpPostedFileBase as a property value.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
|
||||
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
|
||||
/// <param name="value">The <see cref="HttpPostedFileBase"/> containing the file that will be uploaded</param>
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="value">The uploaded <see cref="HttpPostedFileBase"/>.</param>
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value)
|
||||
{
|
||||
// Ensure we get the filename without the path in IE in intranet mode
|
||||
// ensure we get the filename without the path in IE in intranet mode
|
||||
// http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
|
||||
var fileName = value.FileName;
|
||||
if (fileName.LastIndexOf(@"\") > 0)
|
||||
fileName = fileName.Substring(fileName.LastIndexOf(@"\") + 1);
|
||||
var filename = value.FileName;
|
||||
var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture);
|
||||
if (pos > 0)
|
||||
filename = filename.Substring(pos + 1);
|
||||
|
||||
var name =
|
||||
IOHelper.SafeFileName(
|
||||
fileName.Substring(fileName.LastIndexOf(IOHelper.DirSepChar) + 1,
|
||||
fileName.Length - fileName.LastIndexOf(IOHelper.DirSepChar) - 1)
|
||||
.ToLower());
|
||||
// strip any directory info
|
||||
pos = filename.LastIndexOf(IOHelper.DirSepChar);
|
||||
if (pos > 0)
|
||||
filename = filename.Substring(pos + 1);
|
||||
|
||||
if (string.IsNullOrEmpty(name) == false)
|
||||
SetFileOnContent(content, propertyTypeAlias, name, value.InputStream);
|
||||
// get a safe filename - should this be done by MediaHelper?
|
||||
filename = IOHelper.SafeFileName(filename);
|
||||
if (string.IsNullOrWhiteSpace(filename)) return;
|
||||
filename = filename.ToLower(); // fixme - er... why?
|
||||
|
||||
FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets and uploads the file from a HttpPostedFile object as the property value
|
||||
/// Stores and sets an uploaded HttpPostedFile as a property value.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
|
||||
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
|
||||
/// <param name="value">The <see cref="HttpPostedFile"/> containing the file that will be uploaded</param>
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="value">The uploaded <see cref="HttpPostedFile"/>.</param>
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value)
|
||||
{
|
||||
SetValue(content, propertyTypeAlias, (HttpPostedFileBase)new HttpPostedFileWrapper(value));
|
||||
SetValue(content, propertyTypeAlias, (HttpPostedFileBase) new HttpPostedFileWrapper(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets and uploads the file from a HttpPostedFileWrapper object as the property value
|
||||
/// Stores and sets an uploaded HttpPostedFileWrapper as a property value.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
|
||||
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
|
||||
/// <param name="value">The <see cref="HttpPostedFileWrapper"/> containing the file that will be uploaded</param>
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="value">The uploaded <see cref="HttpPostedFileWrapper"/>.</param>
|
||||
[Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")]
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileWrapper value)
|
||||
{
|
||||
SetValue(content, propertyTypeAlias, (HttpPostedFileBase)value);
|
||||
SetValue(content, propertyTypeAlias, (HttpPostedFileBase) value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets and uploads the file from a <see cref="Stream"/> as the property value
|
||||
/// Stores and sets a file as a property value.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
|
||||
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
|
||||
/// <param name="fileName">Name of the file</param>
|
||||
/// <param name="fileStream"><see cref="Stream"/> to save to disk</param>
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, string fileName, Stream fileStream)
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="filename">The name of the file.</param>
|
||||
/// <param name="filestream">A stream containing the file data.</param>
|
||||
/// <remarks>This really is for FileUpload fields only, and should be obsoleted. For anything else,
|
||||
/// you need to store the file by yourself using Store and then figure out
|
||||
/// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself.</remarks>
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream)
|
||||
{
|
||||
var name = IOHelper.SafeFileName(fileName);
|
||||
if (filename == null || filestream == null) return;
|
||||
|
||||
if (string.IsNullOrEmpty(name) == false && fileStream != null)
|
||||
SetFileOnContent(content, propertyTypeAlias, name, fileStream);
|
||||
// get a safe filename - should this be done by MediaHelper?
|
||||
filename = IOHelper.SafeFileName(filename);
|
||||
if (string.IsNullOrWhiteSpace(filename)) return;
|
||||
filename = filename.ToLower(); // fixme - er... why?
|
||||
|
||||
FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream);
|
||||
}
|
||||
|
||||
private static void SetFileOnContent(IContentBase content, string propertyTypeAlias, string filename, Stream fileStream)
|
||||
/// <summary>
|
||||
/// Stores a file.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="filename">The name of the file.</param>
|
||||
/// <param name="filestream">A stream containing the file data.</param>
|
||||
/// <param name="filepath">The original file path, if any.</param>
|
||||
/// <returns>The path to the file, relative to the media filesystem.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Does NOT set the property value, so one should probably store the file and then do
|
||||
/// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath).</para>
|
||||
/// <para>The original file path is used, in the old media file path scheme, to try and reuse
|
||||
/// the "folder number" that was assigned to the previous file referenced by the property,
|
||||
/// if any.</para>
|
||||
/// </remarks>
|
||||
public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string filepath)
|
||||
{
|
||||
var property = content.Properties.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
if (property == null)
|
||||
return;
|
||||
|
||||
//TODO: ALl of this naming logic needs to be put into the ImageHelper and then we need to change FileUploadPropertyValueEditor to do the same!
|
||||
|
||||
var numberedFolder = MediaSubfolderCounter.Current.Increment();
|
||||
var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
|
||||
? Path.Combine(numberedFolder.ToString(CultureInfo.InvariantCulture), filename)
|
||||
: numberedFolder + "-" + filename;
|
||||
|
||||
var extension = Path.GetExtension(filename).Substring(1).ToLowerInvariant();
|
||||
|
||||
//the file size is the length of the stream in bytes
|
||||
var fileSize = fileStream.Length;
|
||||
|
||||
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
|
||||
fs.AddFile(fileName, fileStream);
|
||||
|
||||
//Check if file supports resizing and create thumbnails
|
||||
var supportsResizing = UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension);
|
||||
|
||||
//the config section used to auto-fill properties
|
||||
IImagingAutoFillUploadField uploadFieldConfigNode = null;
|
||||
|
||||
//Check for auto fill of additional properties
|
||||
if (UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties != null)
|
||||
{
|
||||
uploadFieldConfigNode = UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties
|
||||
.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
|
||||
}
|
||||
|
||||
if (supportsResizing)
|
||||
{
|
||||
//get the original image from the original stream
|
||||
if (fileStream.CanSeek) fileStream.Seek(0, 0);
|
||||
using (var originalImage = Image.FromStream(fileStream))
|
||||
{
|
||||
var additionalSizes = new List<int>();
|
||||
|
||||
//Look up Prevalues for this upload datatype - if it is an upload datatype - get additional configured sizes
|
||||
if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
|
||||
{
|
||||
//Get Prevalues by the DataType's Id: property.PropertyType.DataTypeId
|
||||
var values = ApplicationContext.Current.Services.DataTypeService.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId);
|
||||
var thumbnailSizes = values.FirstOrDefault();
|
||||
//Additional thumbnails configured as prevalues on the DataType
|
||||
if (thumbnailSizes != null)
|
||||
{
|
||||
foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
int thumbSize;
|
||||
if (thumb != "" && int.TryParse(thumb, out thumbSize))
|
||||
{
|
||||
additionalSizes.Add(thumbSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageHelper.GenerateMediaThumbnails(fs, fileName, extension, originalImage, additionalSizes);
|
||||
|
||||
//while the image is still open, we'll check if we need to auto-populate the image properties
|
||||
if (uploadFieldConfigNode != null)
|
||||
{
|
||||
content.SetValue(uploadFieldConfigNode.WidthFieldAlias, originalImage.Width.ToString(CultureInfo.InvariantCulture));
|
||||
content.SetValue(uploadFieldConfigNode.HeightFieldAlias, originalImage.Height.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//if auto-fill is true, then fill the remaining, non-image properties
|
||||
if (uploadFieldConfigNode != null)
|
||||
{
|
||||
content.SetValue(uploadFieldConfigNode.LengthFieldAlias, fileSize.ToString(CultureInfo.InvariantCulture));
|
||||
content.SetValue(uploadFieldConfigNode.ExtensionFieldAlias, extension);
|
||||
}
|
||||
|
||||
//Set the value of the property to that of the uploaded file's url
|
||||
property.Value = fs.GetUrl(fileName);
|
||||
var propertyType = content.GetContentType()
|
||||
.CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
|
||||
if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + ".");
|
||||
return FileSystemProviderManager.Current.MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -16,6 +16,11 @@ namespace Umbraco.Core.Models.Editors
|
||||
/// </remarks>
|
||||
public class ContentPropertyData
|
||||
{
|
||||
public ContentPropertyData(object value, PreValueCollection preValues)
|
||||
: this(value, preValues, new Dictionary<string, object>())
|
||||
{
|
||||
}
|
||||
|
||||
public ContentPropertyData(object value, PreValueCollection preValues, IDictionary<string, object> additionalData)
|
||||
{
|
||||
Value = value;
|
||||
@@ -28,6 +33,9 @@ namespace Umbraco.Core.Models.Editors
|
||||
/// </summary>
|
||||
public object Value { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The pre-value collection for the content property
|
||||
/// </summary>
|
||||
public PreValueCollection PreValues { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -113,8 +113,10 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
/// </summary>
|
||||
internal virtual void AddingEntity()
|
||||
{
|
||||
CreateDate = DateTime.Now;
|
||||
UpdateDate = DateTime.Now;
|
||||
if (IsPropertyDirty("CreateDate") == false || _createDate == default(DateTime))
|
||||
CreateDate = DateTime.Now;
|
||||
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
|
||||
UpdateDate = CreateDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -122,7 +124,8 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
/// </summary>
|
||||
internal virtual void UpdatingEntity()
|
||||
{
|
||||
UpdateDate = DateTime.Now;
|
||||
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
|
||||
UpdateDate = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
@@ -11,6 +12,9 @@ namespace Umbraco.Core.Models
|
||||
[DataMember]
|
||||
int Id { get; set; }
|
||||
|
||||
[DataMember]
|
||||
Guid Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias of the Property
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public interface IXsltFile : IFile
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
@@ -27,11 +24,12 @@ namespace Umbraco.Core.Models
|
||||
_addedProperties = new List<string>();
|
||||
_removedProperties = new List<string>();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates an item with pre-filled properties
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="useInEditor"></param>
|
||||
/// <param name="cacheDuration"></param>
|
||||
/// <param name="alias"></param>
|
||||
@@ -43,10 +41,11 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="cacheByMember"></param>
|
||||
/// <param name="dontRender"></param>
|
||||
/// <param name="scriptPath"></param>
|
||||
public Macro(int id, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath)
|
||||
public Macro(int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath)
|
||||
: this()
|
||||
{
|
||||
Id = id;
|
||||
Key = key;
|
||||
UseInEditor = useInEditor;
|
||||
CacheDuration = cacheDuration;
|
||||
Alias = alias.ToCleanString(CleanStringType.Alias);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
public MacroProperty()
|
||||
{
|
||||
|
||||
_key = Guid.NewGuid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,6 +30,7 @@ namespace Umbraco.Core.Models
|
||||
_alias = alias;
|
||||
_name = name;
|
||||
_sortOrder = sortOrder;
|
||||
_key = Guid.NewGuid();
|
||||
|
||||
//try to get the new mapped parameter editor
|
||||
var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false);
|
||||
@@ -45,16 +46,18 @@ namespace Umbraco.Core.Models
|
||||
/// Ctor for creating an existing property
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="alias"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="sortOrder"></param>
|
||||
/// <param name="editorAlias"></param>
|
||||
internal MacroProperty(int id, string @alias, string name, int sortOrder, string editorAlias)
|
||||
internal MacroProperty(int id, Guid key, string @alias, string name, int sortOrder, string editorAlias)
|
||||
{
|
||||
_id = id;
|
||||
_alias = alias;
|
||||
_name = name;
|
||||
_sortOrder = sortOrder;
|
||||
_key = key;
|
||||
|
||||
//try to get the new mapped parameter editor
|
||||
var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false);
|
||||
@@ -66,6 +69,7 @@ namespace Umbraco.Core.Models
|
||||
_editorAlias = editorAlias;
|
||||
}
|
||||
|
||||
private Guid _key;
|
||||
private string _alias;
|
||||
private string _name;
|
||||
private int _sortOrder;
|
||||
@@ -76,6 +80,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
private class PropertySelectors
|
||||
{
|
||||
public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo<MacroProperty, Guid>(x => x.Key);
|
||||
public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Alias);
|
||||
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Name);
|
||||
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, int>(x => x.SortOrder);
|
||||
@@ -83,6 +88,16 @@ namespace Umbraco.Core.Models
|
||||
public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.EditorAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Key of the Property
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Guid Key
|
||||
{
|
||||
get { return _key; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias of the Property
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -9,73 +8,56 @@ using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
internal static class MediaExtensions
|
||||
public static class MediaExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Hack: we need to put this in a real place, this is currently just used to render the urls for a media item in the back office
|
||||
/// Gets the url of a media item.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetUrl(this IMedia media, string propertyAlias, ILogger logger)
|
||||
{
|
||||
var propertyType = media.PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyAlias));
|
||||
if (propertyType != null)
|
||||
if (propertyType == null) return string.Empty;
|
||||
|
||||
var val = media.Properties[propertyType];
|
||||
if (val == null) return string.Empty;
|
||||
|
||||
var jsonString = val.Value as string;
|
||||
if (jsonString == null) return string.Empty;
|
||||
|
||||
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
|
||||
return jsonString;
|
||||
|
||||
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)
|
||||
{
|
||||
var val = media.Properties[propertyType];
|
||||
if (val != null)
|
||||
if (jsonString.DetectIsJson() == false)
|
||||
return jsonString;
|
||||
|
||||
try
|
||||
{
|
||||
var jsonString = val.Value as string;
|
||||
if (jsonString != null)
|
||||
{
|
||||
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)
|
||||
{
|
||||
if (jsonString.DetectIsJson())
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
|
||||
if (json["src"] != null)
|
||||
{
|
||||
return json["src"].Value<string>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error<ImageCropperValueConverter>("Could not parse the string " + jsonString + " to a json object", ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return jsonString;
|
||||
}
|
||||
}
|
||||
else if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
|
||||
{
|
||||
return jsonString;
|
||||
}
|
||||
//hrm, without knowing what it is, just adding a string here might not be very nice
|
||||
}
|
||||
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
|
||||
if (json["src"] != null)
|
||||
return json["src"].Value<string>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error<ImageCropperValueConverter>("Could not parse the string " + jsonString + " to a json object", ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// hrm, without knowing what it is, just adding a string here might not be very nice
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hack: we need to put this in a real place, this is currently just used to render the urls for a media item in the back office
|
||||
/// Gets the urls of a media item.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string[] GetUrls(this IMedia media, IContentSection contentSection, ILogger logger)
|
||||
{
|
||||
var links = new List<string>();
|
||||
var autoFillProperties = contentSection.ImageAutoFillProperties.ToArray();
|
||||
if (autoFillProperties.Any())
|
||||
{
|
||||
links.AddRange(
|
||||
autoFillProperties
|
||||
.Select(field => media.GetUrl(field.Alias, logger))
|
||||
.Where(link => link.IsNullOrWhiteSpace() == false));
|
||||
}
|
||||
return links.ToArray();
|
||||
return contentSection.ImageAutoFillProperties
|
||||
.Select(field => media.GetUrl(field.Alias, logger))
|
||||
.Where(link => string.IsNullOrWhiteSpace(link) == false)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("umbracoLock")]
|
||||
[PrimaryKey("id")]
|
||||
[ExplicitColumns]
|
||||
internal class LockDto
|
||||
{
|
||||
public LockDto()
|
||||
{
|
||||
Value = 1;
|
||||
}
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(Name = "PK_umbracoLock")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("value")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public int Value { get; set; }
|
||||
|
||||
[Column("name")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Length(64)]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
@@ -13,6 +14,10 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[PrimaryKeyColumn]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("uniqueId")]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacro_UniqueId")]
|
||||
public Guid UniqueId { get; set; }
|
||||
|
||||
[Column("macroUseInEditor")]
|
||||
[Constraint(Default = "0")]
|
||||
public bool UseInEditor { get; set; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Umbraco.Core.Persistence;
|
||||
using System;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
@@ -11,7 +12,12 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn]
|
||||
public int Id { get; set; }
|
||||
|
||||
|
||||
// important to use column name != cmsMacro.uniqueId (fix in v8)
|
||||
[Column("uniquePropertyId")]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMacroProperty_UniquePropertyId")]
|
||||
public Guid UniqueId { get; set; }
|
||||
|
||||
[Column("editorAlias")]
|
||||
public string EditorAlias { get; set; }
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("umbracoDeployChecksum")]
|
||||
[PrimaryKey("id")]
|
||||
[ExplicitColumns]
|
||||
internal class UmbracoDeployChecksumDto
|
||||
{
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(Name = "PK_umbracoDeployChecksum")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("entityType")]
|
||||
[Length(32)]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoDeployChecksum", ForColumns = "entityType,entityGuid,entityPath")]
|
||||
public string EntityType { get; set; }
|
||||
|
||||
[Column("entityGuid")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public Guid EntityGuid { get; set; }
|
||||
|
||||
[Column("entityPath")]
|
||||
[Length(256)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string EntityPath { get; set; }
|
||||
|
||||
[Column("localChecksum")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Length(32)]
|
||||
public string LocalChecksum { get; set; }
|
||||
|
||||
[Column("compositeChecksum")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(32)]
|
||||
public string CompositeChecksum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("umbracoDeployDependency")]
|
||||
[ExplicitColumns]
|
||||
internal class UmbracoDeployDependencyDto
|
||||
{
|
||||
[Column("sourceId")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false, Clustered = true, Name = "PK_umbracoDeployDependency", OnColumns = "sourceId, targetId")]
|
||||
[ForeignKey(typeof(UmbracoDeployChecksumDto), Name = "FK_umbracoDeployDependency_umbracoDeployChecksum_id1")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public int SourceId { get; set; }
|
||||
|
||||
[Column("targetId")]
|
||||
[ForeignKey(typeof(UmbracoDeployChecksumDto), Name = "FK_umbracoDeployDependency_umbracoDeployChecksum_id2")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public int TargetId { get; set; }
|
||||
|
||||
[Column("mode")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public int Mode { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ using Umbraco.Core.Strings;
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Template file
|
||||
/// Represents a Template file.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Template file that can have its content on disk.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public class TemplateOnDisk : Template
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TemplateOnDisk"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the template.</param>
|
||||
/// <param name="alias">The alias of the template.</param>
|
||||
public TemplateOnDisk(string name, string alias)
|
||||
: base(name, alias)
|
||||
{
|
||||
IsOnDisk = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the content is on disk already.
|
||||
/// </summary>
|
||||
public bool IsOnDisk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Getting the content while the template is "on disk" throws,
|
||||
/// the template must be saved before its content can be retrieved.</para>
|
||||
/// <para>Setting the content means it is not "on disk" anymore, and the
|
||||
/// template becomes (and behaves like) a normal template.</para>
|
||||
/// </remarks>
|
||||
public override string Content
|
||||
{
|
||||
get
|
||||
{
|
||||
return IsOnDisk ? string.Empty : base.Content;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Content = value;
|
||||
IsOnDisk = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a XSLT file
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public class XsltFile : File, IXsltFile
|
||||
{
|
||||
public XsltFile(string path)
|
||||
: this(path, (Func<File, string>) null)
|
||||
{ }
|
||||
|
||||
internal XsltFile(string path, Func<File, string> getFileContent)
|
||||
: base(path, getFileContent)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current entity has an identity, which in this case is a path/name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Overrides the default Entity identity check.
|
||||
/// </remarks>
|
||||
public override bool HasIdentity
|
||||
{
|
||||
get { return string.IsNullOrEmpty(Path) == false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,20 +29,24 @@ namespace Umbraco.Core.Packaging
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> ScanAssembliesForTypeReference<T>(IEnumerable<byte[]> assemblys, out string[] errorReport)
|
||||
{
|
||||
var appDomain = GetTempAppDomain();
|
||||
var type = typeof(PackageBinaryInspector);
|
||||
try
|
||||
// beware! when toying with domains, use a safe call context!
|
||||
using (new SafeCallContext())
|
||||
{
|
||||
var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap(
|
||||
type.Assembly.FullName,
|
||||
type.FullName);
|
||||
// do NOT turn PerformScan into static (even if ReSharper says so)!
|
||||
var result = value.PerformScan<T>(assemblys.ToArray(), out errorReport);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppDomain.Unload(appDomain);
|
||||
var appDomain = GetTempAppDomain();
|
||||
var type = typeof(PackageBinaryInspector);
|
||||
try
|
||||
{
|
||||
var value = (PackageBinaryInspector) appDomain.CreateInstanceAndUnwrap(
|
||||
type.Assembly.FullName,
|
||||
type.FullName);
|
||||
// do NOT turn PerformScan into static (even if ReSharper says so)!
|
||||
var result = value.PerformScan<T>(assemblys.ToArray(), out errorReport);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppDomain.Unload(appDomain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +82,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <summary>
|
||||
/// Performs the assembly scanning
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <param name="errorReport"></param>
|
||||
/// <returns></returns>
|
||||
@@ -107,7 +111,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <summary>
|
||||
/// Performs the assembly scanning
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="dllPath"></param>
|
||||
/// <param name="errorReport"></param>
|
||||
/// <returns></returns>
|
||||
@@ -154,7 +158,7 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
//get the list of assembly names to compare below
|
||||
var loadedNames = loaded.Select(x => x.GetName().Name).ToArray();
|
||||
|
||||
|
||||
//Then load each referenced assembly into the context
|
||||
foreach (var a in loaded)
|
||||
{
|
||||
@@ -170,7 +174,7 @@ namespace Umbraco.Core.Packaging
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
//if an exception occurs it means that a referenced assembly could not be found
|
||||
//if an exception occurs it means that a referenced assembly could not be found
|
||||
errors.Add(
|
||||
string.Concat("This package references the assembly '",
|
||||
assemblyName.Name,
|
||||
@@ -179,7 +183,7 @@ namespace Umbraco.Core.Packaging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//if an exception occurs it means that a referenced assembly could not be found
|
||||
//if an exception occurs it means that a referenced assembly could not be found
|
||||
errors.Add(
|
||||
string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '",
|
||||
assemblyName.Name,
|
||||
@@ -197,7 +201,7 @@ namespace Umbraco.Core.Packaging
|
||||
{
|
||||
//now we need to see if they contain any type 'T'
|
||||
var reflectedAssembly = a;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var found = reflectedAssembly.GetExportedTypes()
|
||||
@@ -210,8 +214,8 @@ namespace Umbraco.Core.Packaging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//This is a hack that nobody can seem to get around, I've read everything and it seems that
|
||||
// this is quite a common thing when loading types into reflection only load context, so
|
||||
//This is a hack that nobody can seem to get around, I've read everything and it seems that
|
||||
// this is quite a common thing when loading types into reflection only load context, so
|
||||
// we're just going to ignore this specific one for now
|
||||
var typeLoadEx = ex as TypeLoadException;
|
||||
if (typeLoadEx != null)
|
||||
@@ -232,7 +236,7 @@ namespace Umbraco.Core.Packaging
|
||||
LogHelper.Error<PackageBinaryInspector>("An error occurred scanning package assemblies", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
errorReport = errors.ToArray();
|
||||
@@ -252,7 +256,7 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
var contractType = contractAssemblyLoadFrom.GetExportedTypes()
|
||||
.FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName);
|
||||
|
||||
|
||||
if (contractType == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies");
|
||||
@@ -281,12 +285,8 @@ namespace Umbraco.Core.Packaging
|
||||
PrivateBinPathProbe = AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe
|
||||
};
|
||||
|
||||
//create new domain with full trust
|
||||
return AppDomain.CreateDomain(
|
||||
appName,
|
||||
AppDomain.CurrentDomain.Evidence,
|
||||
domainSetup,
|
||||
new PermissionSet(PermissionState.Unrestricted));
|
||||
// create new domain with full trust
|
||||
return AppDomain.CreateDomain(appName, AppDomain.CurrentDomain.Evidence, domainSetup, new PermissionSet(PermissionState.Unrestricted));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
static partial class Constants
|
||||
{
|
||||
public static class Locks
|
||||
{
|
||||
public const int Servers = -331;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
#if DEBUG_DATABASES
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
internal static class DatabaseDebugHelper
|
||||
{
|
||||
private const int CommandsSize = 100;
|
||||
private static readonly Queue<Tuple<string, WeakReference<IDbCommand>>> Commands = new Queue<Tuple<string, WeakReference<IDbCommand>>>();
|
||||
|
||||
public static void SetCommand(IDbCommand command, string context)
|
||||
{
|
||||
var prof = command as StackExchange.Profiling.Data.ProfiledDbCommand;
|
||||
if (prof != null) command = prof.InternalCommand;
|
||||
lock (Commands)
|
||||
{
|
||||
Commands.Enqueue(Tuple.Create(context, new WeakReference<IDbCommand>(command)));
|
||||
while (Commands.Count > CommandsSize) Commands.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetCommandContext(IDbCommand command)
|
||||
{
|
||||
lock (Commands)
|
||||
{
|
||||
var tuple = Commands.FirstOrDefault(x =>
|
||||
{
|
||||
IDbCommand c;
|
||||
return x.Item2.TryGetTarget(out c) && c == command;
|
||||
});
|
||||
return tuple == null ? "?" : tuple.Item1;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetReferencedObjects(IDbConnection con)
|
||||
{
|
||||
var prof = con as StackExchange.Profiling.Data.ProfiledDbConnection;
|
||||
if (prof != null) con = prof.InnerConnection;
|
||||
var ceCon = con as System.Data.SqlServerCe.SqlCeConnection;
|
||||
if (ceCon != null) return null; // "NotSupported: SqlCE";
|
||||
var dbCon = con as DbConnection;
|
||||
return dbCon == null
|
||||
? "NotSupported: " + con.GetType()
|
||||
: GetReferencedObjects(dbCon);
|
||||
}
|
||||
|
||||
public static string GetReferencedObjects(DbConnection con)
|
||||
{
|
||||
var t = con.GetType();
|
||||
|
||||
var field = t.GetField("_innerConnection", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (field == null) throw new Exception("panic: _innerConnection (" + t + ").");
|
||||
var innerConnection = field.GetValue(con);
|
||||
|
||||
var tin = innerConnection.GetType();
|
||||
|
||||
var fi = con is System.Data.SqlClient.SqlConnection
|
||||
? tin.BaseType.BaseType.GetField("_referenceCollection", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
: tin.BaseType.GetField("_referenceCollection", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (fi == null)
|
||||
//return "";
|
||||
throw new Exception("panic: referenceCollection.");
|
||||
|
||||
var rc = fi.GetValue(innerConnection);
|
||||
if (rc == null)
|
||||
//return "";
|
||||
throw new Exception("panic: innerCollection.");
|
||||
|
||||
field = rc.GetType().BaseType.GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (field == null) throw new Exception("panic: items.");
|
||||
var items = field.GetValue(rc);
|
||||
var prop = items.GetType().GetProperty("Length", BindingFlags.Instance | BindingFlags.Public);
|
||||
if (prop == null) throw new Exception("panic: Length.");
|
||||
var count = Convert.ToInt32(prop.GetValue(items, null));
|
||||
var miGetValue = items.GetType().GetMethod("GetValue", new[] { typeof(int) });
|
||||
if (miGetValue == null) throw new Exception("panic: GetValue.");
|
||||
|
||||
if (count == 0) return null;
|
||||
|
||||
StringBuilder result = null;
|
||||
var hasb = false;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var referencedObj = miGetValue.Invoke(items, new object[] { i });
|
||||
|
||||
var hasTargetProp = referencedObj.GetType().GetProperty("HasTarget");
|
||||
if (hasTargetProp == null) throw new Exception("panic: HasTarget");
|
||||
var hasTarget = Convert.ToBoolean(hasTargetProp.GetValue(referencedObj, null));
|
||||
if (hasTarget == false) continue;
|
||||
|
||||
if (hasb == false)
|
||||
{
|
||||
result = new StringBuilder();
|
||||
result.AppendLine("ReferencedItems");
|
||||
hasb = true;
|
||||
}
|
||||
|
||||
//var inUseProp = referencedObj.GetType().GetProperty("InUse");
|
||||
//if (inUseProp == null) throw new Exception("panic: InUse.");
|
||||
//var inUse = Convert.ToBoolean(inUseProp.GetValue(referencedObj, null));
|
||||
var inUse = "?";
|
||||
|
||||
var targetProp = referencedObj.GetType().GetProperty("Target");
|
||||
if (targetProp == null) throw new Exception("panic: Target.");
|
||||
var objTarget = targetProp.GetValue(referencedObj, null);
|
||||
|
||||
result.AppendFormat("\tDiff.Item id=\"{0}\" inUse=\"{1}\" type=\"{2}\" hashCode=\"{3}\"" + Environment.NewLine,
|
||||
i, inUse, objTarget.GetType(), objTarget.GetHashCode());
|
||||
|
||||
DbCommand cmd = null;
|
||||
if (objTarget is DbDataReader)
|
||||
{
|
||||
//var rdr = objTarget as DbDataReader;
|
||||
try
|
||||
{
|
||||
cmd = objTarget.GetType().GetProperty("Command", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(objTarget, null) as DbCommand;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result.AppendFormat("\t\tObjTarget: DbDataReader, Exception: {0}" + Environment.NewLine, e);
|
||||
}
|
||||
}
|
||||
else if (objTarget is DbCommand)
|
||||
{
|
||||
cmd = objTarget as DbCommand;
|
||||
}
|
||||
if (cmd == null)
|
||||
{
|
||||
result.AppendFormat("\t\tObjTarget: {0}" + Environment.NewLine, objTarget.GetType());
|
||||
continue;
|
||||
}
|
||||
|
||||
result.AppendFormat("\t\tCommand type=\"{0}\" hashCode=\"{1}\"" + Environment.NewLine,
|
||||
cmd.GetType(), cmd.GetHashCode());
|
||||
|
||||
var context = GetCommandContext(cmd);
|
||||
result.AppendFormat("\t\t\tContext: {0}" + Environment.NewLine, context);
|
||||
|
||||
var properties = cmd.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||
foreach (var pi in properties)
|
||||
{
|
||||
if (pi.PropertyType.IsPrimitive || pi.PropertyType == typeof(string))
|
||||
result.AppendFormat("\t\t\t{0}: {1}" + Environment.NewLine, pi.Name, pi.GetValue(cmd, null));
|
||||
|
||||
if (pi.PropertyType != typeof (DbConnection) || pi.Name != "Connection") continue;
|
||||
|
||||
var con1 = pi.GetValue(cmd, null) as DbConnection;
|
||||
result.AppendFormat("\t\t\tConnection type=\"{0}\" state=\"{1}\" hashCode=\"{2}\"" + Environment.NewLine,
|
||||
con1.GetType(), con1.State, con1.GetHashCode());
|
||||
|
||||
var propertiesCon = con1.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||
foreach (var picon in propertiesCon)
|
||||
{
|
||||
if (picon.PropertyType.IsPrimitive || picon.PropertyType == typeof(string))
|
||||
result.AppendFormat("\t\t\t\t{0}: {1}" + Environment.NewLine, picon.Name, picon.GetValue(con1, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result == null ? null : result.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
ValidateDatabase(database);
|
||||
|
||||
database.Execute("UPDATE umbracoNode SET sortOrder = (CASE WHEN (sortOrder=1) THEN -1 ELSE 1 END) WHERE id=@id",
|
||||
database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
|
||||
new { @id = nodeId });
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
ValidateDatabase(database);
|
||||
|
||||
database.ExecuteScalar<int>("SELECT sortOrder FROM umbracoNode WHERE id=@id",
|
||||
database.ExecuteScalar<int>("SELECT value FROM umbracoLock WHERE id=@id",
|
||||
new { @id = nodeId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
@@ -19,13 +20,24 @@ namespace Umbraco.Core.Persistence
|
||||
private readonly ILogger _logger;
|
||||
public string ConnectionString { get; private set; }
|
||||
public string ProviderName { get; private set; }
|
||||
|
||||
//very important to have ThreadStatic:
|
||||
// see: http://issues.umbraco.org/issue/U4-2172
|
||||
[ThreadStatic]
|
||||
private static volatile UmbracoDatabase _nonHttpInstance;
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
// NO! see notes in v8 HybridAccessorBase
|
||||
//[ThreadStatic]
|
||||
//private static volatile UmbracoDatabase _nonHttpInstance;
|
||||
|
||||
private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory";
|
||||
|
||||
private static UmbracoDatabase NonContextValue
|
||||
{
|
||||
get { return (UmbracoDatabase) CallContext.LogicalGetData(ItemKey); }
|
||||
set
|
||||
{
|
||||
if (value == null) CallContext.FreeNamedDataSlot(ItemKey);
|
||||
else CallContext.LogicalSetData(ItemKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor accepting custom connection string
|
||||
@@ -34,9 +46,12 @@ namespace Umbraco.Core.Persistence
|
||||
/// <param name="logger"></param>
|
||||
public DefaultDatabaseFactory(string connectionStringName, ILogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
Mandate.ParameterNotNullOrEmpty(connectionStringName, "connectionStringName");
|
||||
_connectionStringName = connectionStringName;
|
||||
|
||||
//if (NonContextValue != null) throw new Exception("NonContextValue is not null.");
|
||||
|
||||
_connectionStringName = connectionStringName;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -48,68 +63,264 @@ namespace Umbraco.Core.Persistence
|
||||
/// <param name="logger"></param>
|
||||
public DefaultDatabaseFactory(string connectionString, string providerName, ILogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
Mandate.ParameterNotNullOrEmpty(connectionString, "connectionString");
|
||||
Mandate.ParameterNotNullOrEmpty(providerName, "providerName");
|
||||
ConnectionString = connectionString;
|
||||
|
||||
//if (NonContextValue != null) throw new Exception("NonContextValue is not null.");
|
||||
|
||||
ConnectionString = connectionString;
|
||||
ProviderName = providerName;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public UmbracoDatabase CreateDatabase()
|
||||
{
|
||||
//no http context, create the singleton global object
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
if (_nonHttpInstance == null)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
//double check
|
||||
if (_nonHttpInstance == null)
|
||||
{
|
||||
_nonHttpInstance = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _nonHttpInstance;
|
||||
}
|
||||
UmbracoDatabase database;
|
||||
|
||||
//we have an http context, so only create one per request
|
||||
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false)
|
||||
{
|
||||
HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory),
|
||||
string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger));
|
||||
}
|
||||
return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
// gets or creates a database, using either the call context (if no http context) or
|
||||
// the current request context (http context) to store it. once done using the database,
|
||||
// it should be disposed - which will remove it from whatever context it is currently
|
||||
// stored in. this is automatic with http context because UmbracoDatabase implements
|
||||
// IDisposeOnRequestEnd, but NOT with call context.
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
database = NonContextValue;
|
||||
if (database == null)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
database = NonContextValue;
|
||||
if (database == null)
|
||||
{
|
||||
database = CreateDatabaseInstance(ContextOwner.CallContext);
|
||||
NonContextValue = database;
|
||||
}
|
||||
#if DEBUG_DATABASES
|
||||
else
|
||||
{
|
||||
Log("Get lcc", database);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if DEBUG_DATABASES
|
||||
else
|
||||
{
|
||||
Log("Get lcc", database);
|
||||
}
|
||||
#endif
|
||||
return database;
|
||||
}
|
||||
|
||||
if (HttpContext.Current.Items.Contains(typeof (DefaultDatabaseFactory)) == false)
|
||||
{
|
||||
database = CreateDatabaseInstance(ContextOwner.HttpContext);
|
||||
HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), database);
|
||||
}
|
||||
else
|
||||
{
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
#if DEBUG_DATABASES
|
||||
Log("Get ctx", database);
|
||||
#endif
|
||||
}
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
// called by UmbracoDatabase when disposed, so that the factory can de-list it from context
|
||||
internal void OnDispose(UmbracoDatabase disposing)
|
||||
{
|
||||
var value = disposing;
|
||||
switch (disposing.ContextOwner)
|
||||
{
|
||||
case ContextOwner.CallContext:
|
||||
value = NonContextValue;
|
||||
break;
|
||||
case ContextOwner.HttpContext:
|
||||
value = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
break;
|
||||
}
|
||||
|
||||
if (value != null && value.InstanceId != disposing.InstanceId) throw new Exception("panic: wrong db.");
|
||||
|
||||
switch (disposing.ContextOwner)
|
||||
{
|
||||
case ContextOwner.CallContext:
|
||||
NonContextValue = null;
|
||||
#if DEBUG_DATABASES
|
||||
Log("Clr lcc", disposing);
|
||||
#endif
|
||||
break;
|
||||
case ContextOwner.HttpContext:
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
#if DEBUG_DATABASES
|
||||
Log("Clr ctx", disposing);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
disposing.ContextOwner = ContextOwner.None;
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
_databases.Remove(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
// helps identifying when non-httpContext databases are created by logging the stack trace
|
||||
private void LogCallContextStack()
|
||||
{
|
||||
var trace = Environment.StackTrace;
|
||||
if (trace.IndexOf("ScheduledPublishing") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Scheduled Publishing");
|
||||
else if (trace.IndexOf("TouchServerTask") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Server Registration");
|
||||
else if (trace.IndexOf("LogScrubber") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Log Scrubber");
|
||||
else
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: " + Environment.StackTrace);
|
||||
}
|
||||
|
||||
private readonly List<UmbracoDatabase> _databases = new List<UmbracoDatabase>();
|
||||
|
||||
// helps identifying database leaks by keeping track of all instances
|
||||
public List<UmbracoDatabase> Databases { get { return _databases; } }
|
||||
|
||||
private static void Log(string message, UmbracoDatabase database)
|
||||
{
|
||||
LogHelper.Debug<DefaultDatabaseFactory>(message + " (" + (database == null ? "" : database.InstanceSid) + ").");
|
||||
}
|
||||
#endif
|
||||
|
||||
internal enum ContextOwner
|
||||
{
|
||||
None,
|
||||
HttpContext,
|
||||
CallContext
|
||||
}
|
||||
|
||||
internal UmbracoDatabase CreateDatabaseInstance(ContextOwner contextOwner)
|
||||
{
|
||||
var database = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger);
|
||||
database.ContextOwner = contextOwner;
|
||||
database.DatabaseFactory = this;
|
||||
//database.EnableSqlTrace = true;
|
||||
#if DEBUG_DATABASES
|
||||
Log("Create " + contextOwner, database);
|
||||
if (contextOwner == ContextOwner.CallContext)
|
||||
LogCallContextStack();
|
||||
_databases.Add(database);
|
||||
#endif
|
||||
return database;
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
_nonHttpInstance.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)))
|
||||
{
|
||||
((UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
UmbracoDatabase database;
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
database = NonContextValue;
|
||||
#if DEBUG_DATABASES
|
||||
Log("Release lcc", database);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
#if DEBUG_DATABASES
|
||||
Log("Release ctx", database);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (database != null) database.Dispose(); // removes it from call context
|
||||
}
|
||||
|
||||
// during tests, the thread static var can leak between tests
|
||||
// this method provides a way to force-reset the variable
|
||||
internal void ResetForTests()
|
||||
internal void ResetForTests()
|
||||
{
|
||||
if (_nonHttpInstance == null) return;
|
||||
_nonHttpInstance.Dispose();
|
||||
_nonHttpInstance = null;
|
||||
var value = NonContextValue;
|
||||
if (value != null) value.Dispose();
|
||||
NonContextValue = null;
|
||||
}
|
||||
|
||||
#region SafeCallContext
|
||||
|
||||
// see notes in SafeCallContext - need to do this since we are using
|
||||
// the logical call context...
|
||||
|
||||
static DefaultDatabaseFactory()
|
||||
{
|
||||
SafeCallContext.Register(DetachAmbientDatabase, AttachAmbientDatabase);
|
||||
}
|
||||
|
||||
// gets a value indicating whether there is an ambient database
|
||||
internal static bool HasAmbientDatabase
|
||||
{
|
||||
get
|
||||
{
|
||||
return HttpContext.Current == null
|
||||
? NonContextValue != null
|
||||
: HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// detaches the current database
|
||||
// ie returns the database and remove it from whatever is "context"
|
||||
internal static UmbracoDatabase DetachAmbientDatabase()
|
||||
{
|
||||
UmbracoDatabase database;
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
database = NonContextValue;
|
||||
NonContextValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
HttpContext.Current.Items.Remove(typeof (DefaultDatabaseFactory));
|
||||
}
|
||||
|
||||
if (database != null) database.ContextOwner = ContextOwner.None;
|
||||
return database;
|
||||
}
|
||||
|
||||
// attach a current database
|
||||
// ie assign it to whatever is "context"
|
||||
// throws if there already is a database
|
||||
internal static void AttachAmbientDatabase(object o)
|
||||
{
|
||||
var database = o as UmbracoDatabase;
|
||||
if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o");
|
||||
|
||||
var ambient = DetachAmbientDatabase();
|
||||
if (ambient != null) ambient.Dispose();
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
//if (NonContextValue != null) throw new InvalidOperationException();
|
||||
if (database == null) return;
|
||||
|
||||
NonContextValue = database;
|
||||
database.ContextOwner = ContextOwner.CallContext;
|
||||
}
|
||||
else
|
||||
{
|
||||
//if (HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
|
||||
if (database == null) return;
|
||||
|
||||
HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] = database;
|
||||
database.ContextOwner = ContextOwner.HttpContext;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal class MacroFactory
|
||||
{
|
||||
#region Implementation of IEntityFactory<Language,LanguageDto>
|
||||
|
||||
public IMacro BuildEntity(MacroDto dto)
|
||||
{
|
||||
var model = new Macro(dto.Id, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.Python);
|
||||
|
||||
var model = new Macro(dto.Id, dto.UniqueId, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.Python);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -20,7 +17,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
foreach (var p in dto.MacroPropertyDtos)
|
||||
{
|
||||
model.Properties.Add(new MacroProperty(p.Id, p.Alias, p.Name, p.SortOrder, p.EditorAlias));
|
||||
model.Properties.Add(new MacroProperty(p.Id, p.UniqueId, p.Alias, p.Name, p.SortOrder, p.EditorAlias));
|
||||
}
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
@@ -36,8 +33,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
public MacroDto BuildDto(IMacro entity)
|
||||
{
|
||||
var dto = new MacroDto()
|
||||
{
|
||||
var dto = new MacroDto
|
||||
{
|
||||
UniqueId = entity.Key,
|
||||
Alias = entity.Alias,
|
||||
CacheByPage = entity.CacheByPage,
|
||||
CachePersonalized = entity.CacheByMember,
|
||||
@@ -58,8 +56,6 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
return dto;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private List<MacroPropertyDto> BuildPropertyDtos(IMacro entity)
|
||||
{
|
||||
var list = new List<MacroPropertyDto>();
|
||||
@@ -67,6 +63,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
var text = new MacroPropertyDto
|
||||
{
|
||||
UniqueId = p.Key,
|
||||
Alias = p.Alias,
|
||||
Name = p.Name,
|
||||
Macro = entity.Id,
|
||||
|
||||
@@ -9,9 +9,10 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal class MemberTypeReadOnlyFactory
|
||||
{
|
||||
public IMemberType BuildEntity(MemberTypeReadOnlyDto dto)
|
||||
public IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving)
|
||||
{
|
||||
var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
|
||||
needsSaving = false;
|
||||
|
||||
var memberType = new MemberType(dto.ParentId);
|
||||
|
||||
@@ -47,6 +48,12 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue;
|
||||
|
||||
// beware!
|
||||
// means that we can return a memberType "from database" that has some property types
|
||||
// that do *not* come from the database and therefore are incomplete eg have no key,
|
||||
// no id, no dataTypeDefinitionId - ouch! - better notify caller of the situation
|
||||
needsSaving = true;
|
||||
|
||||
//Add the standard PropertyType to the current list
|
||||
propertyTypes.Add(standardPropertyType.Value);
|
||||
|
||||
|
||||
@@ -33,7 +33,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
CreateUmbracNodeData();
|
||||
}
|
||||
|
||||
if(tableName.Equals("cmsContentType"))
|
||||
if (tableName.Equals("umbracoLock"))
|
||||
{
|
||||
CreateUmbracoLockData();
|
||||
}
|
||||
|
||||
if (tableName.Equals("cmsContentType"))
|
||||
{
|
||||
CreateCmsContentTypeData();
|
||||
}
|
||||
@@ -141,9 +146,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
|
||||
|
||||
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
|
||||
}
|
||||
|
||||
private void CreateUmbracoLockData()
|
||||
{
|
||||
// all lock objects
|
||||
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.ServersLock, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1," + Constants.System.ServersLock, SortOrder = 1, UniqueId = new Guid("0AF5E610-A310-4B6F-925F-E928D5416AF7"), Text = "LOCK: Servers", NodeObjectType = Constants.ObjectTypes.LockObjectGuid, CreateDate = DateTime.Now });
|
||||
_database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" });
|
||||
}
|
||||
|
||||
private void CreateCmsContentTypeData()
|
||||
|
||||
@@ -82,9 +82,10 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
{43, typeof (CacheInstructionDto)},
|
||||
{44, typeof (ExternalLoginDto)},
|
||||
{45, typeof (MigrationDto)},
|
||||
{46, typeof (UmbracoDeployChecksumDto)},
|
||||
{47, typeof (UmbracoDeployDependencyDto)},
|
||||
{48, typeof (RedirectUrlDto) }
|
||||
//46, removed: UmbracoDeployChecksumDto
|
||||
//47, removed: UmbracoDeployDependencyDto
|
||||
{48, typeof (RedirectUrlDto) },
|
||||
{49, typeof (LockDto) }
|
||||
};
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -136,6 +136,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
return new Version(7, 4, 0);
|
||||
}
|
||||
|
||||
//if the error indicates a problem with the column cmsMacroProperty.uniquePropertyId then it is not version 7.6 since that is when it is added
|
||||
if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,uniquePropertyId"))))
|
||||
{
|
||||
return new Version(7, 5, 0);
|
||||
}
|
||||
|
||||
return UmbracoVersion.Current;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
internal IMigrationContext Context;
|
||||
public IMigrationContext Context { get; internal set; }
|
||||
|
||||
public abstract void Up();
|
||||
public abstract void Down();
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core.Persistence.Migrations.Syntax.Create.Expressions;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Create.ForeignKey;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Create.Index;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Create.Table;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions;
|
||||
using Umbraco.Core.Persistence.Migrations.Syntax.Expressions;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
@@ -27,6 +28,24 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create
|
||||
_databaseProviders = databaseProviders;
|
||||
}
|
||||
|
||||
public void Table<T>()
|
||||
{
|
||||
var tableDefinition = DefinitionFactory.GetTableDefinition(_sqlSyntax, typeof(T));
|
||||
|
||||
AddSql(_sqlSyntax.Format(tableDefinition));
|
||||
AddSql(_sqlSyntax.FormatPrimaryKey(tableDefinition));
|
||||
foreach (var sql in _sqlSyntax.Format(tableDefinition.ForeignKeys))
|
||||
AddSql(sql);
|
||||
foreach (var sql in _sqlSyntax.Format(tableDefinition.Indexes))
|
||||
AddSql(sql);
|
||||
}
|
||||
|
||||
private void AddSql(string sql)
|
||||
{
|
||||
var expression = new ExecuteSqlStatementExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { SqlStatement = sql };
|
||||
_context.Expressions.Add(expression);
|
||||
}
|
||||
|
||||
public ICreateTableWithColumnSyntax Table(string tableName)
|
||||
{
|
||||
var expression = new CreateTableExpression(_context.CurrentDatabaseProvider, _databaseProviders, _sqlSyntax) { TableName = tableName };
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Create
|
||||
{
|
||||
public interface ICreateBuilder : IFluentSyntax
|
||||
{
|
||||
void Table<T>();
|
||||
|
||||
ICreateTableWithColumnSyntax Table(string tableName);
|
||||
ICreateColumnOnTableSyntax Column(string columnName);
|
||||
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive
|
||||
{
|
||||
[Migration("7.5.5", 101, GlobalSettings.UmbracoMigrationName)]
|
||||
public class AddLockObjects : MigrationBase
|
||||
{
|
||||
public AddLockObjects(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
EnsureLockObject(Constants.Locks.Servers, "Servers");
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
|
||||
private void EnsureLockObject(int id, string name)
|
||||
{
|
||||
Execute.Code(db =>
|
||||
{
|
||||
var exists = db.Exists<LockDto>(id);
|
||||
if (exists) return string.Empty;
|
||||
// be safe: delete old umbracoNode lock objects if any
|
||||
db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id });
|
||||
// then create umbracoLock object
|
||||
db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, @name, 1);", new { id, name });
|
||||
return string.Empty;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive
|
||||
{
|
||||
[Migration("7.5.5", 100, GlobalSettings.UmbracoMigrationName)]
|
||||
public class AddLockTable : MigrationBase
|
||||
{
|
||||
public AddLockTable(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
if (tables.InvariantContains("umbracoLock") == false)
|
||||
{
|
||||
Create.Table("umbracoLock")
|
||||
.WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock")
|
||||
.WithColumn("value").AsInt32().NotNullable()
|
||||
.WithColumn("name").AsString(64).NotNullable();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer
|
||||
// for some reason it was not, so it was created during migrations but not during
|
||||
// new installs, so for ppl that upgrade, make sure they have it
|
||||
|
||||
EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
|
||||
EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
{
|
||||
[Migration("7.6.0", 0, GlobalSettings.UmbracoMigrationName)]
|
||||
public class AddMacroUniqueIdColumn : MigrationBase
|
||||
{
|
||||
public AddMacroUniqueIdColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("cmsMacro") && x.ColumnName.InvariantEquals("uniqueId")) == false)
|
||||
{
|
||||
Create.Column("uniqueId").OnTable("cmsMacro").AsGuid().Nullable();
|
||||
Execute.Code(UpdateMacroGuids);
|
||||
Alter.Table("cmsMacro").AlterColumn("uniqueId").AsGuid().NotNullable();
|
||||
Create.Index("IX_cmsMacro_UniqueId").OnTable("cmsMacro").OnColumn("uniqueId")
|
||||
.Ascending()
|
||||
.WithOptions().NonClustered()
|
||||
.WithOptions().Unique();
|
||||
|
||||
}
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("cmsMacroProperty") && x.ColumnName.InvariantEquals("uniquePropertyId")) == false)
|
||||
{
|
||||
Create.Column("uniquePropertyId").OnTable("cmsMacroProperty").AsGuid().Nullable();
|
||||
Execute.Code(UpdateMacroPropertyGuids);
|
||||
Alter.Table("cmsMacroProperty").AlterColumn("uniquePropertyId").AsGuid().NotNullable();
|
||||
Create.Index("IX_cmsMacroProperty_UniquePropertyId").OnTable("cmsMacroProperty").OnColumn("uniquePropertyId")
|
||||
.Ascending()
|
||||
.WithOptions().NonClustered()
|
||||
.WithOptions().Unique();
|
||||
}
|
||||
}
|
||||
|
||||
private static string UpdateMacroGuids(Database database)
|
||||
{
|
||||
var updates = database.Query<dynamic>("SELECT id, macroAlias FROM cmsMacro")
|
||||
.Select(macro => Tuple.Create((int) macro.id, ((string) macro.macroAlias).ToGuid()))
|
||||
.ToList();
|
||||
|
||||
foreach (var update in updates)
|
||||
database.Execute("UPDATE cmsMacro set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 });
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string UpdateMacroPropertyGuids(Database database)
|
||||
{
|
||||
var updates = database.Query<dynamic>(@"SELECT cmsMacroProperty.id id, macroPropertyAlias propertyAlias, cmsMacro.macroAlias macroAlias
|
||||
FROM cmsMacroProperty
|
||||
JOIN cmsMacro ON cmsMacroProperty.macro=cmsMacro.id")
|
||||
.Select(prop => Tuple.Create((int) prop.id, ((string) prop.macroAlias + "____" + (string) prop.propertyAlias).ToGuid()))
|
||||
.ToList();
|
||||
|
||||
foreach (var update in updates)
|
||||
database.Execute("UPDATE cmsMacroProperty set uniquePropertyId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 });
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
{
|
||||
[Migration("7.6.0", 0, GlobalSettings.UmbracoMigrationName)]
|
||||
public class RemoveUmbracoDeployTables : MigrationBase
|
||||
{
|
||||
public RemoveUmbracoDeployTables(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
|
||||
// there are two versions of umbracoDeployDependency,
|
||||
// 1. one created by 7.4 and never used, we need to remove it (has a sourceId column)
|
||||
// 2. one created by Deploy itself, we need to keep it (has a sourceUdi column)
|
||||
if (tables.InvariantContains("umbracoDeployDependency"))
|
||||
{
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("umbracoDeployDependency") && x.ColumnName.InvariantEquals("sourceId")))
|
||||
Delete.Table("umbracoDeployDependency");
|
||||
}
|
||||
|
||||
// always remove umbracoDeployChecksum
|
||||
if (tables.InvariantContains("umbracoDeployChecksum"))
|
||||
Delete.Table("umbracoDeployChecksum");
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe
|
||||
Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0);
|
||||
}
|
||||
|
||||
EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
|
||||
EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace Umbraco.Core.Persistence
|
||||
CommonConstruct();
|
||||
}
|
||||
|
||||
internal enum DBType
|
||||
public enum DBType
|
||||
{
|
||||
SqlServer,
|
||||
SqlServerCE,
|
||||
@@ -190,7 +190,9 @@ namespace Umbraco.Core.Persistence
|
||||
Oracle,
|
||||
SQLite
|
||||
}
|
||||
DBType _dbType = DBType.SqlServer;
|
||||
private DBType _dbType = DBType.SqlServer;
|
||||
|
||||
public DBType DatabaseType { get { return _dbType; } }
|
||||
|
||||
// Common initialization
|
||||
private void CommonConstruct()
|
||||
@@ -224,13 +226,18 @@ namespace Umbraco.Core.Persistence
|
||||
// Automatically close one open shared connection
|
||||
public void Dispose()
|
||||
{
|
||||
// Automatically close one open connection reference
|
||||
// (Works with KeepConnectionAlive and manually opening a shared connection)
|
||||
CloseSharedConnection();
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
// Set to true to keep the first opened connection alive until this object is disposed
|
||||
public bool KeepConnectionAlive { get; set; }
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
// Automatically close one open connection reference
|
||||
// (Works with KeepConnectionAlive and manually opening a shared connection)
|
||||
CloseSharedConnection();
|
||||
}
|
||||
|
||||
// Set to true to keep the first opened connection alive until this object is disposed
|
||||
public bool KeepConnectionAlive { get; set; }
|
||||
|
||||
// Open a connection (can be nested)
|
||||
public void OpenSharedConnection()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
@@ -57,6 +59,26 @@ namespace Umbraco.Core.Persistence
|
||||
return sql.Where(fieldName + " IN (@values)", new { values });
|
||||
}
|
||||
|
||||
[Obsolete("Use the overload specifying ISqlSyntaxProvider instead")]
|
||||
public static Sql WhereAnyIn<TDto>(this Sql sql, Expression<Func<TDto, object>>[] fieldSelectors, IEnumerable values)
|
||||
{
|
||||
return sql.WhereAnyIn(fieldSelectors, values, SqlSyntaxContext.SqlSyntaxProvider);
|
||||
}
|
||||
|
||||
public static Sql WhereAnyIn<TDto>(this Sql sql, Expression<Func<TDto, object>>[] fieldSelectors, IEnumerable values, ISqlSyntaxProvider sqlSyntax)
|
||||
{
|
||||
var fieldNames = fieldSelectors.Select(x => GetFieldName(x, sqlSyntax)).ToArray();
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("(");
|
||||
for (var i = 0; i < fieldNames.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(" OR ");
|
||||
sb.Append(fieldNames[i] + " IN (@values)");
|
||||
}
|
||||
sb.Append(")");
|
||||
return sql.Where(sb.ToString(), new { values });
|
||||
}
|
||||
|
||||
[Obsolete("Use the overload specifying ISqlSyntaxProvider instead")]
|
||||
public static Sql OrderBy<TColumn>(this Sql sql, Expression<Func<TColumn, object>> columnMember)
|
||||
{
|
||||
|
||||
@@ -3,48 +3,6 @@ using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Relators
|
||||
{
|
||||
//internal class TaskUserRelator
|
||||
//{
|
||||
// internal TaskDto Current;
|
||||
|
||||
// internal TaskDto Map(TaskDto a, UserDto p)
|
||||
// {
|
||||
// // Terminating call. Since we can return null from this function
|
||||
// // we need to be ready for PetaPoco to callback later with null
|
||||
// // parameters
|
||||
// if (a == null)
|
||||
// return Current;
|
||||
|
||||
// // Is this the same TaskDto as the current one we're processing
|
||||
// if (Current != null && Current.Id == a.Id)
|
||||
// {
|
||||
// // Yes, set the user
|
||||
// Current.MacroPropertyDtos.Add(p);
|
||||
|
||||
// // Return null to indicate we're not done with this Macro yet
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// // This is a different Macro to the current one, or this is the
|
||||
// // first time through and we don't have one yet
|
||||
|
||||
// // Save the current Macro
|
||||
// var prev = Current;
|
||||
|
||||
// // Setup the new current Macro
|
||||
// Current = a;
|
||||
// Current.MacroPropertyDtos = new List<MacroPropertyDto>();
|
||||
// //this can be null since we are doing a left join
|
||||
// if (p.Alias != null)
|
||||
// {
|
||||
// Current.MacroPropertyDtos.Add(p);
|
||||
// }
|
||||
|
||||
// // Return the now populated previous Macro (or null if first time through)
|
||||
// return prev;
|
||||
// }
|
||||
//}
|
||||
|
||||
internal class MacroPropertyRelator
|
||||
{
|
||||
internal MacroDto Current;
|
||||
|
||||
@@ -445,7 +445,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.UpdateDate = DateTime.Now;
|
||||
if (entity.IsPropertyDirty("UpdateDate") == false || entity.UpdateDate == default(DateTime))
|
||||
entity.UpdateDate = DateTime.Now;
|
||||
}
|
||||
|
||||
//Ensure unique name on the same level
|
||||
|
||||
@@ -217,10 +217,20 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
protected string GetFileContent(string filename)
|
||||
{
|
||||
using (var stream = FileSystem.OpenFile(filename))
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8, true))
|
||||
if (FileSystem.FileExists(filename) == false)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
using (var stream = FileSystem.OpenFile(filename))
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8, true))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null; // deal with race conds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal interface IMacroRepository : IRepositoryQueryable<int, IMacro>
|
||||
internal interface IMacroRepository : IRepositoryQueryable<int, IMacro>, IReadRepository<Guid, IMacro>
|
||||
{
|
||||
|
||||
//IEnumerable<IMacro> GetAll(params string[] aliases);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Umbraco.Core.Models;
|
||||
using System.IO;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
@@ -7,5 +8,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
void AddFolder(string folderPath);
|
||||
void DeleteFolder(string folderPath);
|
||||
bool ValidatePartialView(IPartialView partialView);
|
||||
Stream GetFileContentStream(string filepath);
|
||||
void SetFileContent(string filepath, Stream content);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
using Umbraco.Core.Models;
|
||||
using System.IO;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IScriptRepository : IRepository<string, Script>
|
||||
{
|
||||
bool ValidateScript(Script script);
|
||||
Stream GetFileContentStream(string filepath);
|
||||
void SetFileContent(string filepath, Stream content);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
@@ -6,5 +6,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public interface IStylesheetRepository : IRepository<string, Stylesheet>
|
||||
{
|
||||
bool ValidateStylesheet(Stylesheet stylesheet);
|
||||
Stream GetFileContentStream(string filepath);
|
||||
void SetFileContent(string filepath, Stream content);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
@@ -57,5 +58,19 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <param name="template"><see cref="ITemplate"/> to validate</param>
|
||||
/// <returns>True if Script is valid, otherwise false</returns>
|
||||
bool ValidateTemplate(ITemplate template);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a template as a stream.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the template.</param>
|
||||
/// <returns>The content of the template.</returns>
|
||||
Stream GetFileContentStream(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of a template.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the template.</param>
|
||||
/// <param name="content">The content of the template.</param>
|
||||
void SetFileContent(string filepath, Stream content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.IO;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IXsltFileRepository : IRepository<string, XsltFile>
|
||||
{
|
||||
bool ValidateXsltFile(XsltFile xsltFile);
|
||||
Stream GetFileContentStream(string filepath);
|
||||
void SetFileContent(string filepath, Stream content);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,17 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
var sql = GetBaseQuery(false);
|
||||
sql.Where(GetBaseWhereClause(), new { Id = id });
|
||||
return GetBySql(sql);
|
||||
}
|
||||
|
||||
public IMacro Get(Guid id)
|
||||
{
|
||||
var sql = GetBaseQuery().Where("uniqueId=@Id", new { Id = id });
|
||||
return GetBySql(sql);
|
||||
}
|
||||
|
||||
private IMacro GetBySql(Sql sql)
|
||||
{
|
||||
var macroDto = Database.Fetch<MacroDto, MacroPropertyDto, MacroDto>(new MacroPropertyRelator().Map, sql).FirstOrDefault();
|
||||
if (macroDto == null)
|
||||
return null;
|
||||
@@ -41,26 +51,26 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return entity;
|
||||
}
|
||||
|
||||
protected override IEnumerable<IMacro> PerformGetAll(params int[] ids)
|
||||
public IEnumerable<IMacro> GetAll(params Guid[] ids)
|
||||
{
|
||||
if (ids.Any())
|
||||
{
|
||||
return PerformGetAllOnIds(ids);
|
||||
}
|
||||
|
||||
var sql = GetBaseQuery(false);
|
||||
|
||||
return ConvertFromDtos(Database.Fetch<MacroDto, MacroPropertyDto, MacroDto>(new MacroPropertyRelator().Map, sql))
|
||||
.ToArray();// we don't want to re-iterate again!
|
||||
return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds();
|
||||
}
|
||||
|
||||
private IEnumerable<IMacro> PerformGetAllOnIds(params int[] ids)
|
||||
public bool Exists(Guid id)
|
||||
{
|
||||
if (ids.Any() == false) yield break;
|
||||
foreach (var id in ids)
|
||||
{
|
||||
yield return Get(id);
|
||||
}
|
||||
return Get(id) != null;
|
||||
}
|
||||
|
||||
protected override IEnumerable<IMacro> PerformGetAll(params int[] ids)
|
||||
{
|
||||
return ids.Length > 0 ? ids.Select(Get) : GetAllNoIds();
|
||||
}
|
||||
|
||||
private IEnumerable<IMacro> GetAllNoIds()
|
||||
{
|
||||
var sql = GetBaseQuery(false);
|
||||
return ConvertFromDtos(Database.Fetch<MacroDto, MacroPropertyDto, MacroDto>(new MacroPropertyRelator().Map, sql))
|
||||
.ToArray();// we don't want to re-iterate again!
|
||||
}
|
||||
|
||||
private IEnumerable<IMacro> ConvertFromDtos(IEnumerable<MacroDto> dtos)
|
||||
@@ -124,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var list = new List<string>
|
||||
{
|
||||
"DELETE FROM cmsMacroProperty WHERE macro = @Id",
|
||||
"DELETE FROM cmsMacro WHERE id = @Id"
|
||||
"DELETE FROM cmsMacro WHERE id = @Id"
|
||||
};
|
||||
return list;
|
||||
}
|
||||
@@ -146,7 +156,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
foreach (var propDto in dto.MacroPropertyDtos)
|
||||
{
|
||||
//need to set the id explicitly here
|
||||
// need to set the id explicitly here
|
||||
propDto.Macro = id;
|
||||
var propId = Convert.ToInt32(Database.Insert(propDto));
|
||||
entity.Properties[propDto.Alias].Id = propId;
|
||||
@@ -165,71 +175,60 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
Database.Update(dto);
|
||||
|
||||
//update the properties if they've changed
|
||||
var macro = (Macro)entity;
|
||||
var macro = (Macro) entity;
|
||||
if (macro.IsPropertyDirty("Properties") || macro.Properties.Any(x => x.IsDirty()))
|
||||
{
|
||||
//now we need to delete any props that have been removed
|
||||
foreach (var propAlias in macro.RemovedProperties)
|
||||
{
|
||||
//delete the property
|
||||
Database.Delete<MacroPropertyDto>("WHERE macro=@macroId AND macroPropertyAlias=@propAlias",
|
||||
new { macroId = macro.Id, propAlias = propAlias });
|
||||
}
|
||||
var ids = dto.MacroPropertyDtos.Where(x => x.Id > 0).Select(x => x.Id).ToArray();
|
||||
if (ids.Length > 0)
|
||||
Database.Delete<MacroPropertyDto>("WHERE macro=@macro AND id NOT IN (@ids)", new { macro = dto.Id, ids });
|
||||
else
|
||||
Database.Delete<MacroPropertyDto>("WHERE macro=@macro", new { macro = dto.Id });
|
||||
|
||||
//for any that exist on the object, we need to determine if we need to update or insert
|
||||
// detect new aliases, replace with temp aliases
|
||||
// this ensures that we don't have collisions, ever
|
||||
var aliases = new Dictionary<string, string>();
|
||||
foreach (var propDto in dto.MacroPropertyDtos)
|
||||
{
|
||||
if (macro.AddedProperties.Contains(propDto.Alias))
|
||||
var prop = macro.Properties.FirstOrDefault(x => x.Id == propDto.Id);
|
||||
if (prop == null) throw new Exception("oops: property.");
|
||||
if (propDto.Id == 0 || prop.IsPropertyDirty("Alias"))
|
||||
{
|
||||
//we need to insert since this was added and re-assign the new id
|
||||
var propId = Convert.ToInt32(Database.Insert(propDto));
|
||||
macro.Properties[propDto.Alias].Id = propId;
|
||||
var tempAlias = Guid.NewGuid().ToString("N").Substring(0, 8);
|
||||
aliases[tempAlias] = propDto.Alias;
|
||||
propDto.Alias = tempAlias;
|
||||
}
|
||||
}
|
||||
|
||||
// insert or update existing properties, with temp aliases
|
||||
foreach (var propDto in dto.MacroPropertyDtos)
|
||||
{
|
||||
if (propDto.Id == 0)
|
||||
{
|
||||
// insert
|
||||
propDto.Id = Convert.ToInt32(Database.Insert(propDto));
|
||||
macro.Properties[aliases[propDto.Alias]].Id = propDto.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
//This will only work if the Alias hasn't been changed
|
||||
if (macro.Properties.ContainsKey(propDto.Alias))
|
||||
{
|
||||
//only update if it's dirty
|
||||
if (macro.Properties[propDto.Alias].IsDirty())
|
||||
{
|
||||
Database.Update(propDto);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var property = macro.Properties.FirstOrDefault(x => x.Id == propDto.Id);
|
||||
if (property != null && property.IsDirty())
|
||||
{
|
||||
Database.Update(propDto);
|
||||
}
|
||||
}
|
||||
// update
|
||||
var property = macro.Properties.FirstOrDefault(x => x.Id == propDto.Id);
|
||||
if (property == null) throw new Exception("oops: property.");
|
||||
if (property.IsDirty())
|
||||
Database.Update(propDto);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// replace the temp aliases with the real ones
|
||||
foreach (var propDto in dto.MacroPropertyDtos)
|
||||
{
|
||||
if (aliases.ContainsKey(propDto.Alias) == false) continue;
|
||||
|
||||
propDto.Alias = aliases[propDto.Alias];
|
||||
Database.Update(propDto);
|
||||
}
|
||||
}
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
//public IEnumerable<IMacro> GetAll(params string[] aliases)
|
||||
//{
|
||||
// if (aliases.Any())
|
||||
// {
|
||||
// var q = new Query<IMacro>();
|
||||
// foreach (var alias in aliases)
|
||||
// {
|
||||
// q.Where(macro => macro.Alias == alias);
|
||||
// }
|
||||
|
||||
// var wheres = string.Join(" OR ", q.WhereClauses());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return GetAll(new int[] {});
|
||||
// }
|
||||
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -300,13 +300,19 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// </summary>
|
||||
/// <param name="dtos"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<IMemberType> BuildFromDtos(List<MemberTypeReadOnlyDto> dtos)
|
||||
private IEnumerable<IMemberType> BuildFromDtos(List<MemberTypeReadOnlyDto> dtos)
|
||||
{
|
||||
if (dtos == null || dtos.Any() == false)
|
||||
return Enumerable.Empty<IMemberType>();
|
||||
|
||||
var factory = new MemberTypeReadOnlyFactory();
|
||||
return dtos.Select(factory.BuildEntity);
|
||||
return dtos.Select(x =>
|
||||
{
|
||||
bool needsSaving;
|
||||
var memberType = factory.BuildEntity(x, out needsSaving);
|
||||
if (needsSaving) PersistUpdatedItem(memberType);
|
||||
return memberType;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Threading;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
@@ -7,15 +6,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal class PartialViewMacroRepository : PartialViewRepository
|
||||
{
|
||||
public PartialViewMacroRepository(IUnitOfWork work)
|
||||
: this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/MacroPartials/"))
|
||||
{
|
||||
}
|
||||
|
||||
public PartialViewMacroRepository(IUnitOfWork work, IFileSystem fileSystem)
|
||||
: base(work, fileSystem)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
protected override PartialViewType ViewType { get { return PartialViewType.PartialViewMacro; } }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -11,14 +10,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal class PartialViewRepository : FileRepository<string, IPartialView>, IPartialViewRepository
|
||||
{
|
||||
public PartialViewRepository(IUnitOfWork work)
|
||||
: this(work, new PhysicalFileSystem(SystemDirectories.MvcViews + "/Partials/"))
|
||||
{
|
||||
}
|
||||
|
||||
public PartialViewRepository(IUnitOfWork work, IFileSystem fileSystem) : base(work, fileSystem)
|
||||
{
|
||||
}
|
||||
public PartialViewRepository(IUnitOfWork work, IFileSystem fileSystem)
|
||||
: base(work, fileSystem)
|
||||
{ }
|
||||
|
||||
protected virtual PartialViewType ViewType { get { return PartialViewType.PartialView; } }
|
||||
|
||||
@@ -113,6 +107,25 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return isValidPath && isValidExtension;
|
||||
}
|
||||
|
||||
public Stream GetFileContentStream(string filepath)
|
||||
{
|
||||
if (FileSystem.FileExists(filepath) == false) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return FileSystem.OpenFile(filepath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null; // deal with race conds
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFileContent(string filepath, Stream content)
|
||||
{
|
||||
FileSystem.AddFile(filepath, content, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a stream that is used to write to the file
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
@@ -109,6 +110,25 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return isValidPath && isValidExtension;
|
||||
}
|
||||
|
||||
public Stream GetFileContentStream(string filepath)
|
||||
{
|
||||
if (FileSystem.FileExists(filepath) == false) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return FileSystem.OpenFile(filepath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null; // deal with race conds
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFileContent(string filepath, Stream content)
|
||||
{
|
||||
FileSystem.AddFile(filepath, content, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,25 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return isValidPath && isValidExtension;
|
||||
}
|
||||
|
||||
public Stream GetFileContentStream(string filepath)
|
||||
{
|
||||
if (FileSystem.FileExists(filepath) == false) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return FileSystem.OpenFile(filepath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null; // deal with race conds
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFileContent(string filepath, Stream content)
|
||||
{
|
||||
FileSystem.AddFile(filepath, content, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
_viewsFileSystem = viewFileSystem;
|
||||
_templateConfig = templateConfig;
|
||||
_viewHelper = new ViewHelper(_viewsFileSystem);
|
||||
_masterPageHelper = new MasterPageHelper(_masterpagesFileSystem);
|
||||
_masterPageHelper = new MasterPageHelper(_masterpagesFileSystem);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
if (dtos.Count == 0) return Enumerable.Empty<ITemplate>();
|
||||
|
||||
//look up the simple template definitions that have a master template assigned, this is used
|
||||
//look up the simple template definitions that have a master template assigned, this is used
|
||||
// later to populate the template item's properties
|
||||
var childIds = (ids.Any()
|
||||
? GetAxisDefinitions(dtos.ToArray())
|
||||
@@ -91,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
ParentId = x.NodeDto.ParentId,
|
||||
Name = x.Alias
|
||||
})).ToArray();
|
||||
|
||||
|
||||
return dtos.Select(d => MapFromDto(d, childIds));
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
if (dtos.Count == 0) return Enumerable.Empty<ITemplate>();
|
||||
|
||||
//look up the simple template definitions that have a master template assigned, this is used
|
||||
//look up the simple template definitions that have a master template assigned, this is used
|
||||
// later to populate the template item's properties
|
||||
var childIds = GetAxisDefinitions(dtos.ToArray()).ToArray();
|
||||
|
||||
@@ -188,29 +188,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
template.Path = nodeDto.Path;
|
||||
|
||||
//now do the file work
|
||||
|
||||
if (DetermineTemplateRenderingEngine(entity) == RenderingEngine.Mvc)
|
||||
{
|
||||
var result = _viewHelper.CreateView(template, true);
|
||||
if (result != entity.Content)
|
||||
{
|
||||
entity.Content = result;
|
||||
//re-persist it... though we don't really care about the templates in the db do we??!!
|
||||
dto.Design = result;
|
||||
Database.Update(dto);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = _masterPageHelper.CreateMasterPage(template, this, true);
|
||||
if (result != entity.Content)
|
||||
{
|
||||
entity.Content = result;
|
||||
//re-persist it... though we don't really care about the templates in the db do we??!!
|
||||
dto.Design = result;
|
||||
Database.Update(dto);
|
||||
}
|
||||
}
|
||||
SaveFile(template, dto);
|
||||
|
||||
template.ResetDirtyProperties();
|
||||
|
||||
@@ -241,7 +219,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
//this means that the master template has been removed, so we need to reset the template's
|
||||
//path to be at the root
|
||||
entity.Path = string.Concat("-1,", entity.Id);
|
||||
}
|
||||
}
|
||||
|
||||
//Get TemplateDto from db to get the Primary key of the entity
|
||||
@@ -260,29 +243,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
template.IsMasterTemplate = axisDefs.Any(x => x.ParentId == dto.NodeId);
|
||||
|
||||
//now do the file work
|
||||
|
||||
if (DetermineTemplateRenderingEngine(entity) == RenderingEngine.Mvc)
|
||||
{
|
||||
var result = _viewHelper.UpdateViewFile(entity, originalAlias);
|
||||
if (result != entity.Content)
|
||||
{
|
||||
entity.Content = result;
|
||||
//re-persist it... though we don't really care about the templates in the db do we??!!
|
||||
dto.Design = result;
|
||||
Database.Update(dto);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = _masterPageHelper.UpdateMasterPageFile(entity, originalAlias, this);
|
||||
if (result != entity.Content)
|
||||
{
|
||||
entity.Content = result;
|
||||
//re-persist it... though we don't really care about the templates in the db do we??!!
|
||||
dto.Design = result;
|
||||
Database.Update(dto);
|
||||
}
|
||||
}
|
||||
SaveFile((Template) entity, dto, originalAlias);
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
@@ -291,6 +252,43 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
template.GetFileContent = file => GetFileContent((Template) file, false);
|
||||
}
|
||||
|
||||
private void SaveFile(Template template, TemplateDto dto, string originalAlias = null)
|
||||
{
|
||||
string content;
|
||||
|
||||
var templateOnDisk = template as TemplateOnDisk;
|
||||
if (templateOnDisk != null && templateOnDisk.IsOnDisk)
|
||||
{
|
||||
// if "template on disk" load content from disk
|
||||
content = _viewHelper.GetFileContents(template);
|
||||
}
|
||||
else
|
||||
{
|
||||
// else, create or write template.Content to disk
|
||||
if (DetermineTemplateRenderingEngine(template) == RenderingEngine.Mvc)
|
||||
{
|
||||
content = originalAlias == null
|
||||
? _viewHelper.CreateView(template, true)
|
||||
: _viewHelper.UpdateViewFile(template, originalAlias);
|
||||
}
|
||||
else
|
||||
{
|
||||
content = originalAlias == null
|
||||
? _masterPageHelper.CreateMasterPage(template, this, true)
|
||||
: _masterPageHelper.UpdateMasterPageFile(template, originalAlias, this);
|
||||
}
|
||||
}
|
||||
|
||||
// once content has been set, "template on disk" are not "on disk" anymore
|
||||
template.Content = content;
|
||||
|
||||
if (dto.Design == content) return;
|
||||
dto.Design = content;
|
||||
Database.Update(dto); // though... we don't care about the db value really??!!
|
||||
|
||||
SetVirtualPath(template);
|
||||
}
|
||||
|
||||
protected override void PersistDeletedItem(ITemplate entity)
|
||||
{
|
||||
var deletes = GetDeleteClauses().ToArray();
|
||||
@@ -324,14 +322,14 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
var masterpageName = string.Concat(entity.Alias, ".master");
|
||||
_masterpagesFileSystem.DeleteFile(masterpageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private IEnumerable<IUmbracoEntity> GetAxisDefinitions(params TemplateDto[] templates)
|
||||
{
|
||||
//look up the simple template definitions that have a master template assigned, this is used
|
||||
//look up the simple template definitions that have a master template assigned, this is used
|
||||
// later to populate the template item's properties
|
||||
var childIdsSql = new Sql()
|
||||
.Select("nodeId,alias,parentID")
|
||||
@@ -340,7 +338,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
.On<TemplateDto, NodeDto>(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId)
|
||||
//lookup axis's
|
||||
.Where("umbracoNode." + SqlSyntax.GetQuotedColumnName("id") + " IN (@parentIds) OR umbracoNode.parentID IN (@childIds)",
|
||||
new {parentIds = templates.Select(x => x.NodeDto.ParentId), childIds = templates.Select(x => x.NodeId)});
|
||||
new {parentIds = templates.Select(x => x.NodeDto.ParentId), childIds = templates.Select(x => x.NodeId)});
|
||||
|
||||
var childIds = Database.Fetch<dynamic>(childIdsSql)
|
||||
.Select(x => new UmbracoEntity
|
||||
@@ -386,6 +384,50 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return template;
|
||||
}
|
||||
|
||||
private void SetVirtualPath(ITemplate template)
|
||||
{
|
||||
var path = template.OriginalPath;
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
// we need to discover the path
|
||||
path = string.Concat(template.Alias, ".cshtml");
|
||||
if (_viewsFileSystem.FileExists(path))
|
||||
{
|
||||
template.VirtualPath = _viewsFileSystem.GetUrl(path);
|
||||
return;
|
||||
}
|
||||
path = string.Concat(template.Alias, ".vbhtml");
|
||||
if (_viewsFileSystem.FileExists(path))
|
||||
{
|
||||
template.VirtualPath = _viewsFileSystem.GetUrl(path);
|
||||
return;
|
||||
}
|
||||
path = string.Concat(template.Alias, ".master");
|
||||
if (_masterpagesFileSystem.FileExists(path))
|
||||
{
|
||||
template.VirtualPath = _masterpagesFileSystem.GetUrl(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we know the path already
|
||||
var ext = Path.GetExtension(path);
|
||||
switch (ext)
|
||||
{
|
||||
case ".cshtml":
|
||||
case ".vbhtml":
|
||||
template.VirtualPath = _viewsFileSystem.GetUrl(path);
|
||||
return;
|
||||
case ".master":
|
||||
template.VirtualPath = _masterpagesFileSystem.GetUrl(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template.VirtualPath = string.Empty; // file not found...
|
||||
}
|
||||
|
||||
private string GetFileContent(ITemplate template, bool init)
|
||||
{
|
||||
var path = template.OriginalPath;
|
||||
@@ -413,20 +455,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return GetFileContent(template, _viewsFileSystem, path, init);
|
||||
case ".master":
|
||||
return GetFileContent(template, _masterpagesFileSystem, path, init);
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
var fsname = string.Concat(template.Alias, ".cshtml");
|
||||
if (_viewsFileSystem.FileExists(fsname))
|
||||
return GetFileContent(template, _viewsFileSystem, fsname, init);
|
||||
fsname = string.Concat(template.Alias, ".vbhtml");
|
||||
if (_viewsFileSystem.FileExists(fsname))
|
||||
return GetFileContent(template, _viewsFileSystem, fsname, init);
|
||||
fsname = string.Concat(template.Alias, ".master");
|
||||
if (_masterpagesFileSystem.FileExists(fsname))
|
||||
return GetFileContent(template, _masterpagesFileSystem, fsname, init);
|
||||
template.VirtualPath = string.Empty; // file not found...
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
@@ -461,6 +493,45 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetFileContentStream(string filepath)
|
||||
{
|
||||
var fs = GetFileSystem(filepath);
|
||||
if (fs.FileExists(filepath) == false) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return GetFileSystem(filepath).OpenFile(filepath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null; // deal with race conds
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFileContent(string filepath, Stream content)
|
||||
{
|
||||
GetFileSystem(filepath).AddFile(filepath, content, true);
|
||||
}
|
||||
|
||||
private IFileSystem GetFileSystem(string filepath)
|
||||
{
|
||||
var ext = Path.GetExtension(filepath);
|
||||
IFileSystem fs;
|
||||
switch (ext)
|
||||
{
|
||||
case ".cshtml":
|
||||
case ".vbhtml":
|
||||
fs = _viewsFileSystem;
|
||||
break;
|
||||
case ".master":
|
||||
fs = _masterpagesFileSystem;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unsupported extension " + ext + ".");
|
||||
}
|
||||
return fs;
|
||||
}
|
||||
|
||||
#region Implementation of ITemplateRepository
|
||||
|
||||
public ITemplate Get(string alias)
|
||||
@@ -526,7 +597,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//return the list - it will be naturally ordered by level
|
||||
return descendants;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<ITemplate> GetDescendants(string alias)
|
||||
{
|
||||
var all = base.GetAll().ToArray();
|
||||
@@ -570,7 +641,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use GetDescendants instead")]
|
||||
public TemplateNode GetTemplateNode(string alias)
|
||||
{
|
||||
{
|
||||
//first get all template objects
|
||||
var allTemplates = base.GetAll().ToArray();
|
||||
|
||||
@@ -579,7 +650,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var top = selfTemplate;
|
||||
while (top.MasterTemplateAlias.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
@@ -630,16 +701,16 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This checks what the default rendering engine is set in config but then also ensures that there isn't already
|
||||
/// a template that exists in the opposite rendering engine's template folder, then returns the appropriate
|
||||
/// This checks what the default rendering engine is set in config but then also ensures that there isn't already
|
||||
/// a template that exists in the opposite rendering engine's template folder, then returns the appropriate
|
||||
/// rendering engine to use.
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx
|
||||
/// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml
|
||||
/// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page.
|
||||
/// This is mostly related to installing packages since packages install file templates to the file system and then create the
|
||||
/// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml
|
||||
/// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page.
|
||||
/// This is mostly related to installing packages since packages install file templates to the file system and then create the
|
||||
/// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package.
|
||||
/// </remarks>
|
||||
public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template)
|
||||
@@ -747,7 +818,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <param name="template"></param>
|
||||
private void EnsureValidAlias(ITemplate template)
|
||||
{
|
||||
//ensure unique alias
|
||||
//ensure unique alias
|
||||
template.Alias = template.Alias.ToCleanString(CleanStringType.UnderscoreAlias);
|
||||
|
||||
if (template.Alias.Length > 100)
|
||||
|
||||
@@ -22,6 +22,7 @@ using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Dynamics;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Media;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
@@ -560,9 +561,7 @@ WHERE EXISTS(
|
||||
|
||||
var preVals = new PreValueCollection(asDictionary);
|
||||
|
||||
var contentPropData = new ContentPropertyData(property.Value,
|
||||
preVals,
|
||||
new Dictionary<string, object>());
|
||||
var contentPropData = new ContentPropertyData(property.Value, preVals);
|
||||
|
||||
TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport);
|
||||
}
|
||||
@@ -656,7 +655,7 @@ WHERE EXISTS(
|
||||
|
||||
var allsuccess = true;
|
||||
|
||||
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
|
||||
var fs = FileSystemProviderManager.Current.MediaFileSystem;
|
||||
Parallel.ForEach(files, file =>
|
||||
{
|
||||
try
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the XsltFile Repository
|
||||
/// </summary>
|
||||
internal class XsltFileRepository : FileRepository<string, XsltFile>, IXsltFileRepository
|
||||
{
|
||||
public XsltFileRepository(IUnitOfWork work, IFileSystem fileSystem)
|
||||
: base(work, fileSystem)
|
||||
{
|
||||
}
|
||||
|
||||
public override XsltFile Get(string id)
|
||||
{
|
||||
var path = FileSystem.GetRelativePath(id);
|
||||
|
||||
path = path.EnsureEndsWith(".xslt");
|
||||
|
||||
if (FileSystem.FileExists(path) == false)
|
||||
return null;
|
||||
|
||||
var created = FileSystem.GetCreated(path).UtcDateTime;
|
||||
var updated = FileSystem.GetLastModified(path).UtcDateTime;
|
||||
|
||||
var xsltFile = new XsltFile(path, file => GetFileContent(file.OriginalPath))
|
||||
{
|
||||
Key = path.EncodeAsGuid(),
|
||||
CreateDate = created,
|
||||
UpdateDate = updated,
|
||||
Id = path.GetHashCode(),
|
||||
VirtualPath = FileSystem.GetUrl(path)
|
||||
};
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
xsltFile.ResetDirtyProperties(false);
|
||||
|
||||
return xsltFile;
|
||||
}
|
||||
|
||||
public override void AddOrUpdate(XsltFile entity)
|
||||
{
|
||||
base.AddOrUpdate(entity);
|
||||
|
||||
// ensure that from now on, content is lazy-loaded
|
||||
if (entity.GetFileContent == null)
|
||||
entity.GetFileContent = file => GetFileContent(file.OriginalPath);
|
||||
}
|
||||
|
||||
public override IEnumerable<XsltFile> GetAll(params string[] ids)
|
||||
{
|
||||
ids = ids
|
||||
.Select(x => x.EnsureEndsWith(".xslt"))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
if (ids.Any())
|
||||
{
|
||||
foreach (var id in ids)
|
||||
{
|
||||
yield return Get(id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var files = FindAllFiles("", "*.xslt");
|
||||
foreach (var file in files)
|
||||
{
|
||||
yield return Get(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all <see cref="XsltFile"/> that exist at the relative path specified.
|
||||
/// </summary>
|
||||
/// <param name="rootPath">
|
||||
/// If null or not specified, will return the XSLT files at the root path relative to the IFileSystem
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<XsltFile> GetXsltFilesAtPath(string rootPath = null)
|
||||
{
|
||||
return FileSystem.GetFiles(rootPath ?? string.Empty, "*.xslt").Select(Get);
|
||||
}
|
||||
|
||||
private static readonly List<string> ValidExtensions = new List<string> { "xslt" };
|
||||
|
||||
public bool ValidateXsltFile(XsltFile xsltFile)
|
||||
{
|
||||
// get full path
|
||||
string fullPath;
|
||||
try
|
||||
{
|
||||
// may throw for security reasons
|
||||
fullPath = FileSystem.GetFullPath(xsltFile.Path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate path and extension
|
||||
var validDir = SystemDirectories.Xslt;
|
||||
var isValidPath = IOHelper.VerifyEditPath(fullPath, validDir);
|
||||
var isValidExtension = IOHelper.VerifyFileExtension(xsltFile.Path, ValidExtensions);
|
||||
return isValidPath && isValidExtension;
|
||||
}
|
||||
|
||||
public Stream GetFileContentStream(string filepath)
|
||||
{
|
||||
if (FileSystem.FileExists(filepath) == false) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return FileSystem.OpenFile(filepath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null; // deal with race conds
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFileContent(string filepath, Stream content)
|
||||
{
|
||||
FileSystem.AddFile(filepath, content, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,22 +212,22 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
public virtual IScriptRepository CreateScriptRepository(IUnitOfWork uow)
|
||||
{
|
||||
return new ScriptRepository(uow, new PhysicalFileSystem(SystemDirectories.Scripts), _settings.Content);
|
||||
return new ScriptRepository(uow, FileSystemProviderManager.Current.ScriptsFileSystem, _settings.Content);
|
||||
}
|
||||
|
||||
internal virtual IPartialViewRepository CreatePartialViewRepository(IUnitOfWork uow)
|
||||
{
|
||||
return new PartialViewRepository(uow);
|
||||
return new PartialViewRepository(uow, FileSystemProviderManager.Current.PartialViewsFileSystem);
|
||||
}
|
||||
|
||||
internal virtual IPartialViewRepository CreatePartialViewMacroRepository(IUnitOfWork uow)
|
||||
{
|
||||
return new PartialViewMacroRepository(uow);
|
||||
return new PartialViewMacroRepository(uow, FileSystemProviderManager.Current.MacroPartialsFileSystem);
|
||||
}
|
||||
|
||||
public virtual IStylesheetRepository CreateStylesheetRepository(IUnitOfWork uow, IDatabaseUnitOfWork db)
|
||||
{
|
||||
return new StylesheetRepository(uow, new PhysicalFileSystem(SystemDirectories.Css));
|
||||
return new StylesheetRepository(uow, FileSystemProviderManager.Current.StylesheetsFileSystem);
|
||||
}
|
||||
|
||||
public virtual ITemplateRepository CreateTemplateRepository(IDatabaseUnitOfWork uow)
|
||||
@@ -235,11 +235,16 @@ namespace Umbraco.Core.Persistence
|
||||
return new TemplateRepository(uow,
|
||||
_cacheHelper,
|
||||
_logger, _sqlSyntax,
|
||||
new PhysicalFileSystem(SystemDirectories.Masterpages),
|
||||
new PhysicalFileSystem(SystemDirectories.MvcViews),
|
||||
FileSystemProviderManager.Current.MasterPagesFileSystem,
|
||||
FileSystemProviderManager.Current.MvcViewsFileSystem,
|
||||
_settings.Templates);
|
||||
}
|
||||
|
||||
public virtual IXsltFileRepository CreateXsltFileRepository(IUnitOfWork uow)
|
||||
{
|
||||
return new XsltFileRepository(uow, FileSystemProviderManager.Current.XsltFileSystem);
|
||||
}
|
||||
|
||||
public virtual IMigrationEntryRepository CreateMigrationEntryRepository(IDatabaseUnitOfWork uow)
|
||||
{
|
||||
return new MigrationEntryRepository(
|
||||
@@ -342,5 +347,10 @@ namespace Umbraco.Core.Persistence
|
||||
_logger,
|
||||
_sqlSyntax);
|
||||
}
|
||||
|
||||
internal IStylesheetRepository CreateStylesheetRepository(IDatabaseUnitOfWork uow)
|
||||
{
|
||||
return new StylesheetRepository(uow, FileSystemProviderManager.Current.StylesheetsFileSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using StackExchange.Profiling;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
using System.Threading;
|
||||
#endif
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,6 +25,12 @@ namespace Umbraco.Core.Persistence
|
||||
private readonly ILogger _logger;
|
||||
private readonly Guid _instanceId = Guid.NewGuid();
|
||||
private bool _enableCount;
|
||||
#if DEBUG_DATABASES
|
||||
private int _spid = -1;
|
||||
#endif
|
||||
|
||||
internal DefaultDatabaseFactory.ContextOwner ContextOwner = DefaultDatabaseFactory.ContextOwner.None;
|
||||
internal DefaultDatabaseFactory DatabaseFactory = null;
|
||||
|
||||
/// <summary>
|
||||
/// Used for testing
|
||||
@@ -32,11 +40,29 @@ namespace Umbraco.Core.Persistence
|
||||
get { return _instanceId; }
|
||||
}
|
||||
|
||||
public string InstanceSid
|
||||
{
|
||||
get
|
||||
{
|
||||
#if DEBUG_DATABASES
|
||||
return _instanceId.ToString("N").Substring(0, 8) + ":" + _spid;
|
||||
#else
|
||||
return _instanceId.ToString("N").Substring(0, 8);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generally used for testing, will output all SQL statements executed to the logger
|
||||
/// </summary>
|
||||
internal bool EnableSqlTrace { get; set; }
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
private const bool EnableSqlTraceDefault = true;
|
||||
#else
|
||||
private const bool EnableSqlTraceDefault = false;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Used for testing
|
||||
/// </summary>
|
||||
@@ -87,41 +113,72 @@ namespace Umbraco.Core.Persistence
|
||||
: base(connection)
|
||||
{
|
||||
_logger = logger;
|
||||
EnableSqlTrace = false;
|
||||
EnableSqlTrace = EnableSqlTraceDefault;
|
||||
}
|
||||
|
||||
public UmbracoDatabase(string connectionString, string providerName, ILogger logger)
|
||||
: base(connectionString, providerName)
|
||||
{
|
||||
_logger = logger;
|
||||
EnableSqlTrace = false;
|
||||
EnableSqlTrace = EnableSqlTraceDefault;
|
||||
}
|
||||
|
||||
public UmbracoDatabase(string connectionString, DbProviderFactory provider, ILogger logger)
|
||||
: base(connectionString, provider)
|
||||
{
|
||||
_logger = logger;
|
||||
EnableSqlTrace = false;
|
||||
EnableSqlTrace = EnableSqlTraceDefault;
|
||||
}
|
||||
|
||||
public UmbracoDatabase(string connectionStringName, ILogger logger)
|
||||
: base(connectionStringName)
|
||||
{
|
||||
_logger = logger;
|
||||
EnableSqlTrace = false;
|
||||
EnableSqlTrace = EnableSqlTraceDefault;
|
||||
}
|
||||
|
||||
public override IDbConnection OnConnectionOpened(IDbConnection connection)
|
||||
{
|
||||
// propagate timeout if none yet
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
if (DatabaseType == DBType.MySql)
|
||||
{
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "SELECT CONNECTION_ID()";
|
||||
_spid = Convert.ToInt32(command.ExecuteScalar());
|
||||
}
|
||||
}
|
||||
else if (DatabaseType == DBType.SqlServer)
|
||||
{
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "SELECT @@SPID";
|
||||
_spid = Convert.ToInt32(command.ExecuteScalar());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// includes SqlCE
|
||||
_spid = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// wrap the connection with a profiling connection that tracks timings
|
||||
return new StackExchange.Profiling.Data.ProfiledDbConnection(connection as DbConnection, MiniProfiler.Current);
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
public override void OnConnectionClosing(IDbConnection conn)
|
||||
{
|
||||
_spid = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void OnException(Exception x)
|
||||
{
|
||||
_logger.Error<UmbracoDatabase>("Database exception occurred", x);
|
||||
_logger.Error<UmbracoDatabase>("Exception (" + InstanceSid + ").", x);
|
||||
base.OnException(x);
|
||||
}
|
||||
|
||||
@@ -134,16 +191,26 @@ namespace Umbraco.Core.Persistence
|
||||
if (EnableSqlTrace)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
#if DEBUG_DATABASES
|
||||
sb.Append(InstanceSid);
|
||||
sb.Append(": ");
|
||||
#endif
|
||||
sb.Append(cmd.CommandText);
|
||||
foreach (DbParameter p in cmd.Parameters)
|
||||
{
|
||||
sb.Append(" - ");
|
||||
sb.Append(p.Value);
|
||||
}
|
||||
|
||||
_logger.Debug<UmbracoDatabase>(sb.ToString());
|
||||
|
||||
_logger.Debug<UmbracoDatabase>(sb.ToString().Replace("{", "{{").Replace("}", "}}"));
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
DatabaseDebugHelper.SetCommand(cmd, InstanceSid + " [T" + Thread.CurrentThread.ManagedThreadId + "]");
|
||||
var refsobj = DatabaseDebugHelper.GetReferencedObjects(cmd.Connection);
|
||||
if (refsobj != null) _logger.Debug<UmbracoDatabase>("Oops!" + Environment.NewLine + refsobj);
|
||||
#endif
|
||||
|
||||
base.OnExecutingCommand(cmd);
|
||||
}
|
||||
|
||||
@@ -187,9 +254,18 @@ namespace Umbraco.Core.Persistence
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//use the defaults
|
||||
base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
#if DEBUG_DATABASES
|
||||
LogHelper.Debug<UmbracoDatabase>("Dispose (" + InstanceSid + ").");
|
||||
#endif
|
||||
if (DatabaseFactory != null) DatabaseFactory.OnDispose(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@
|
||||
/// The list of tags will be supplied by the property editor's ConvertEditorToDb method result which will need to return an IEnumerable{string} value
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// if the ConvertEditorToDb doesn't return an IEnumerable{string} then an exception will be thrown.
|
||||
/// if the ConvertEditorToDb doesn't return an IEnumerable{string} then it will automatically try to be detected as either CSV or JSON and if neither of those match
|
||||
/// an exception will be thrown.
|
||||
/// </remarks>
|
||||
CustomTagList
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
internal class SafeCallContext : IDisposable
|
||||
{
|
||||
private static readonly List<Func<object>> EnterFuncs = new List<Func<object>>();
|
||||
private static readonly List<Action<object>> ExitActions = new List<Action<object>>();
|
||||
private static int _count;
|
||||
private readonly List<object> _objects;
|
||||
private bool _disposed;
|
||||
|
||||
public static void Register(Func<object> enterFunc, Action<object> exitAction)
|
||||
{
|
||||
if (enterFunc == null) throw new ArgumentNullException("enterFunc");
|
||||
if (exitAction == null) throw new ArgumentNullException("exitAction");
|
||||
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
if (_count > 0) throw new InvalidOperationException("Cannot register while some SafeCallContext instances exist.");
|
||||
EnterFuncs.Add(enterFunc);
|
||||
ExitActions.Add(exitAction);
|
||||
}
|
||||
}
|
||||
|
||||
// tried to make the UmbracoDatabase serializable but then it leaks to weird places
|
||||
// in ReSharper and so on, where Umbraco.Core is not available. Tried to serialize
|
||||
// as an object instead but then it comes *back* deserialized into the original context
|
||||
// as an object and of course it breaks everything. Cannot prevent this from flowing,
|
||||
// and ExecutionContext.SuppressFlow() works for threads but not domains. and we'll
|
||||
// have the same issue with anything that toys with logical call context...
|
||||
//
|
||||
// so this class lets anything that uses the logical call context register itself,
|
||||
// providing two methods:
|
||||
// - an enter func that removes and returns whatever is in the logical call context
|
||||
// - an exit action that restores the value into the logical call context
|
||||
// whenever a SafeCallContext instance is created, it uses these methods to capture
|
||||
// and clear the logical call context, and restore it when disposed.
|
||||
//
|
||||
// in addition, a static Clear method is provided - which uses the enter funcs to
|
||||
// remove everything from logical call context - not to be used when the app runs,
|
||||
// but can be useful during tests
|
||||
//
|
||||
// note
|
||||
// see System.Transactions
|
||||
// they are using a conditional weak table to store the data, and what they store in
|
||||
// LLC is the key - which is just an empty MarshalByRefObject that is created with
|
||||
// the transaction scope - that way, they can "clear current data" provided that
|
||||
// they have the key - but they need to hold onto a ref to the scope... not ok for us
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
foreach (var enter in EnterFuncs)
|
||||
enter();
|
||||
}
|
||||
}
|
||||
|
||||
public SafeCallContext()
|
||||
{
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
_count++;
|
||||
_objects = EnterFuncs.Select(x => x()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException("this");
|
||||
_disposed = true;
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
for (var i = 0; i < ExitActions.Count; i++)
|
||||
ExitActions[i](_objects[i]);
|
||||
_count--;
|
||||
}
|
||||
}
|
||||
|
||||
// for unit tests ONLY
|
||||
internal static void Reset()
|
||||
{
|
||||
lock (EnterFuncs)
|
||||
{
|
||||
if (_count > 0) throw new InvalidOperationException("Cannot reset while some SafeCallContext instances exist.");
|
||||
EnterFuncs.Clear();
|
||||
ExitActions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ namespace Umbraco.Core.Services
|
||||
case UmbracoObjectTypes.ROOT:
|
||||
case UmbracoObjectTypes.Unknown:
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
throw new NotSupportedException("Unsupported object type (" + umbracoObjectType + ").");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,12 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Auditing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -558,8 +553,72 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetTemplateFileContentStream(string filepath)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateTemplateRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetFileContentStream(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTemplateFileContent(string filepath, Stream content)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateTemplateRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
repository.SetFileContent(filepath, content);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public Stream GetStylesheetFileContentStream(string filepath)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateStylesheetRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetFileContentStream(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStylesheetFileContent(string filepath, Stream content)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateStylesheetRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
repository.SetFileContent(filepath, content);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetScriptFileContentStream(string filepath)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateScriptRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetFileContentStream(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetScriptFileContent(string filepath, Stream content)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateScriptRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
repository.SetFileContent(filepath, content);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetXsltFileContentStream(string filepath)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateXsltFileRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetFileContentStream(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetXsltFileContent(string filepath, Stream content)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateXsltFileRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
repository.SetFileContent(filepath, content);
|
||||
}
|
||||
}
|
||||
|
||||
#region Partial Views
|
||||
|
||||
public IEnumerable<string> GetPartialViewSnippetNames(params string[] filterNames)
|
||||
@@ -614,6 +673,30 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IPartialView> GetPartialViewMacros(params string[] names)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreatePartialViewMacroRepository(_fileUowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetAll(names).OrderBy(x => x.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public IXsltFile GetXsltFile(string path)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateXsltFileRepository(_fileUowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.Get(path);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IXsltFile> GetXsltFiles(params string[] names)
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateXsltFileRepository(_fileUowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetAll(names).OrderBy(x => x.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public Attempt<IPartialView> CreatePartialView(IPartialView partialView, string snippetName = null, int userId = 0)
|
||||
{
|
||||
return CreatePartialViewMacro(partialView, PartialViewType.PartialView, snippetName, userId);
|
||||
@@ -791,6 +874,38 @@ namespace Umbraco.Core.Services
|
||||
throw new ArgumentOutOfRangeException("partialViewType");
|
||||
}
|
||||
|
||||
public Stream GetPartialViewMacroFileContentStream(string filepath)
|
||||
{
|
||||
using (var repository = GetPartialViewRepository(PartialViewType.PartialViewMacro, UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetFileContentStream(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPartialViewMacroFileContent(string filepath, Stream content)
|
||||
{
|
||||
using (var repository = GetPartialViewRepository(PartialViewType.PartialViewMacro, UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
repository.SetFileContent(filepath, content);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetPartialViewFileContentStream(string filepath)
|
||||
{
|
||||
using (var repository = GetPartialViewRepository(PartialViewType.PartialView, UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repository.GetFileContentStream(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPartialViewFileContent(string filepath, Stream content)
|
||||
{
|
||||
using (var repository = GetPartialViewRepository(PartialViewType.PartialView, UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
repository.SetFileContent(filepath, content);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Audit(AuditType type, string message, int userId, int objectId)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
@@ -14,6 +15,9 @@ namespace Umbraco.Core.Services
|
||||
void DeletePartialViewMacroFolder(string folderPath);
|
||||
IPartialView GetPartialView(string path);
|
||||
IPartialView GetPartialViewMacro(string path);
|
||||
IEnumerable<IPartialView> GetPartialViewMacros(params string[] names);
|
||||
IXsltFile GetXsltFile(string path);
|
||||
IEnumerable<IXsltFile> GetXsltFiles(params string[] names);
|
||||
Attempt<IPartialView> CreatePartialView(IPartialView partialView, string snippetName = null, int userId = 0);
|
||||
Attempt<IPartialView> CreatePartialViewMacro(IPartialView partialView, string snippetName = null, int userId = 0);
|
||||
bool DeletePartialView(string path, int userId = 0);
|
||||
@@ -235,5 +239,89 @@ namespace Umbraco.Core.Services
|
||||
/// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package.
|
||||
/// </remarks>
|
||||
RenderingEngine DetermineTemplateRenderingEngine(ITemplate template);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a template as a stream.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the template.</param>
|
||||
/// <returns>The content of the template.</returns>
|
||||
Stream GetTemplateFileContentStream(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of a template.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the template.</param>
|
||||
/// <param name="content">The content of the template.</param>
|
||||
void SetTemplateFileContent(string filepath, Stream content);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a stylesheet as a stream.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the stylesheet.</param>
|
||||
/// <returns>The content of the stylesheet.</returns>
|
||||
Stream GetStylesheetFileContentStream(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of a stylesheet.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the stylesheet.</param>
|
||||
/// <param name="content">The content of the stylesheet.</param>
|
||||
void SetStylesheetFileContent(string filepath, Stream content);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a script file as a stream.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the script.</param>
|
||||
/// <returns>The content of the script file.</returns>
|
||||
Stream GetScriptFileContentStream(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of a script file.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the script.</param>
|
||||
/// <param name="content">The content of the script file.</param>
|
||||
void SetScriptFileContent(string filepath, Stream content);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a XSLT file as a stream.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the XSLT file.</param>
|
||||
/// <returns>The content of the XSLT file.</returns>
|
||||
Stream GetXsltFileContentStream(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of a XSLT file.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the XSLT file.</param>
|
||||
/// <param name="content">The content of the XSLT file.</param>
|
||||
void SetXsltFileContent(string filepath, Stream content);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a macro partial view as a stream.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the macro partial view.</param>
|
||||
/// <returns>The content of the macro partial view.</returns>
|
||||
Stream GetPartialViewMacroFileContentStream(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of a macro partial view.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the macro partial view.</param>
|
||||
/// <param name="content">The content of the macro partial view.</param>
|
||||
void SetPartialViewMacroFileContent(string filepath, Stream content);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a partial view as a stream.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the partial view.</param>
|
||||
/// <returns>The content of the partial view.</returns>
|
||||
Stream GetPartialViewFileContentStream(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of a partial view.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the partial view.</param>
|
||||
/// <param name="content">The content of the partial view.</param>
|
||||
void SetPartialViewFileContent(string filepath, Stream content);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
@@ -15,7 +16,7 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="alias">Alias to retrieve an <see cref="IMacro"/> for</param>
|
||||
/// <returns>An <see cref="IMacro"/> object</returns>
|
||||
IMacro GetByAlias(string alias);
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// Gets a list all available <see cref="IMacro"/> objects
|
||||
///// </summary>
|
||||
@@ -23,10 +24,16 @@ namespace Umbraco.Core.Services
|
||||
///// <returns>An enumerable list of <see cref="IMacro"/> objects</returns>
|
||||
//IEnumerable<IMacro> GetAll(params string[] aliases);
|
||||
|
||||
IEnumerable<IMacro> GetAll();
|
||||
|
||||
IEnumerable<IMacro> GetAll(params int[] ids);
|
||||
|
||||
IEnumerable<IMacro> GetAll(params Guid[] ids);
|
||||
|
||||
IMacro GetById(int id);
|
||||
|
||||
IMacro GetById(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an <see cref="IMacro"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using System.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
@@ -414,5 +415,33 @@ namespace Umbraco.Core.Services
|
||||
/// <param name="userId">Optional id of the user creating the media item</param>
|
||||
/// <returns><see cref="IMedia"/></returns>
|
||||
IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a media as a stream.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the media.</param>
|
||||
/// <returns>The content of the media.</returns>
|
||||
Stream GetMediaFileContentStream(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content of a media.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the media.</param>
|
||||
/// <param name="content">The content of the media.</param>
|
||||
void SetMediaFileContent(string filepath, Stream content);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a media file and all thumbnails.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem path to the media.</param>
|
||||
void DeleteMediaFile(string filepath);
|
||||
|
||||
/// <summary>
|
||||
/// Generates thumbnails.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem-relative path to the original image.</param>
|
||||
/// <param name="propertyType">The property type.</param>
|
||||
/// <remarks>This should be obsoleted, we should not generate thumbnails.</remarks>
|
||||
void GenerateThumbnails(string filepath, PropertyType propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user