mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-29 12:40:46 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a2caab7fc | ||
|
|
8db90b6c2f | ||
|
|
1c18fab68e | ||
|
|
42b59cd066 | ||
|
|
a47b85ff85 | ||
|
|
0f1b3f05f9 | ||
|
|
01482e8dd0 | ||
|
|
771d8b3391 | ||
|
|
9d17c5d791 | ||
|
|
0fe0e1a84d | ||
|
|
5d542fbf5e | ||
|
|
72979ee309 | ||
|
|
9054f03556 | ||
|
|
6db61d164f | ||
|
|
9c7b9a6c7a | ||
|
|
2b16198f22 | ||
|
|
88bb631eea | ||
|
|
6c22724835 | ||
|
|
ab57163695 | ||
|
|
e31b0403cd | ||
|
|
2642268383 | ||
|
|
690dca0bd7 | ||
|
|
28ba310b67 | ||
|
|
a58b1bd4a3 | ||
|
|
9259043e6d | ||
|
|
2d5b736e74 | ||
|
|
9fbbfe618b | ||
|
|
3ace90432f | ||
|
|
acd80a724d | ||
|
|
57edf48d13 | ||
|
|
eac5d2029f | ||
|
|
bfc9d8f66a | ||
|
|
c01f588541 | ||
|
|
cda211a3d6 | ||
|
|
b527ee56b7 | ||
|
|
a3145251de | ||
|
|
5c2f0edadc | ||
|
|
af22b5158a | ||
|
|
80d4f377b0 | ||
|
|
c611d1dee8 | ||
|
|
56d430046a | ||
|
|
30e9f36889 | ||
|
|
11ceb1098f | ||
|
|
031208f096 | ||
|
|
ba37ab445d | ||
|
|
7edbe4a82f | ||
|
|
4d0bdf6789 | ||
|
|
e98fe07c02 | ||
|
|
2e4142c93e | ||
|
|
38c9848509 | ||
|
|
8960478939 |
82
.github/workflows/ci.yml
vendored
82
.github/workflows/ci.yml
vendored
@@ -5,7 +5,7 @@ on: [push, pull_request]
|
||||
env:
|
||||
CONFIGURATION: Release
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_SDK_VERSION: 3.1.201
|
||||
DOTNET_SDK_VERSION: 3.1.300
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
|
||||
GITHUB_JOBS: 2 # 2-core CPU, without HT: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#supported-runners-and-hardware-resources
|
||||
NET_CORE_VERSION: netcoreapp3.1
|
||||
@@ -56,13 +56,19 @@ jobs:
|
||||
run: npm run-script build:ci --no-progress --prefix ASF-ui
|
||||
|
||||
- name: Build ArchiSteamFarm
|
||||
run: dotnet build ArchiSteamFarm -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" --nologo
|
||||
run: dotnet build ArchiSteamFarm -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" -p:UseAppHost=false --nologo
|
||||
|
||||
- name: Build ArchiSteamFarm.CustomPlugins.ExamplePlugin
|
||||
run: dotnet build ArchiSteamFarm.CustomPlugins.ExamplePlugin -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" --nologo
|
||||
run: dotnet build ArchiSteamFarm.CustomPlugins.ExamplePlugin -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" -p:UseAppHost=false --nologo
|
||||
|
||||
- name: Run ArchiSteamFarm.Tests
|
||||
run: dotnet test ArchiSteamFarm.Tests -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" --nologo
|
||||
run: dotnet test ArchiSteamFarm.Tests -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" -p:UseAppHost=false --nologo
|
||||
|
||||
- name: Perform cleanup in preparation for publishing
|
||||
run: dotnet clean ArchiSteamFarm -c "${{ env.CONFIGURATION }}" -f "${{ env.NET_CORE_VERSION }}" -p:UseAppHost=false --nologo
|
||||
|
||||
- name: Restore packages in preparation for publishing
|
||||
run: dotnet restore ArchiSteamFarm
|
||||
|
||||
- name: Publish ArchiSteamFarm on Unix
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
@@ -76,7 +82,7 @@ jobs:
|
||||
if [ "$1" = 'generic' ]; then
|
||||
local variantArgs="-p:UseAppHost=false"
|
||||
else
|
||||
local variantArgs="-p:PublishTrimmed=true -r $1"
|
||||
local variantArgs="-p:PublishSingleFile=true -p:PublishTrimmed=true -r $1"
|
||||
fi
|
||||
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o "out/${1}" "-p:ASFVariant=$1" --no-restore --nologo $variantArgs
|
||||
@@ -88,10 +94,19 @@ jobs:
|
||||
|
||||
# Include .ico file for all platforms, since only Windows script can bundle it inside the exe
|
||||
cp "resources/ASF.ico" "out/${1}/ArchiSteamFarm.ico"
|
||||
}
|
||||
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" --nologo
|
||||
dotnet restore ArchiSteamFarm
|
||||
if command -v 7z >/dev/null; then
|
||||
7z a -bd -slp -tzip -mm=Deflate -mx=1 "out/ASF-${1}.zip" "${GITHUB_WORKSPACE}/out/${1}/*"
|
||||
elif command -v zip >/dev/null; then
|
||||
(
|
||||
cd "${GITHUB_WORKSPACE}/out/${1}"
|
||||
zip -1 -q -r "../ASF-${1}.zip" .
|
||||
)
|
||||
else
|
||||
echo "ERROR: No supported zip tool!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
jobs=""
|
||||
|
||||
@@ -132,7 +147,7 @@ jobs:
|
||||
if ($variant -like 'generic*') {
|
||||
$variantArgs = '-p:UseAppHost=false'
|
||||
} else {
|
||||
$variantArgs = '-p:PublishTrimmed=true', '-r', "$variant"
|
||||
$variantArgs = '-p:PublishSingleFile=true', '-p:PublishTrimmed=true', '-r', "$variant"
|
||||
}
|
||||
|
||||
dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -f "$targetFramework" -o "out\$variant" "-p:ASFVariant=$variant" --no-restore --nologo $variantArgs
|
||||
@@ -152,18 +167,12 @@ jobs:
|
||||
Copy-Item 'resources\ASF.ico' "out\$variant\ArchiSteamFarm.ico"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dotnet clean ArchiSteamFarm -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" --nologo
|
||||
7z a -bd -slp -tzip -mm=Deflate -mx=1 "out\ASF-$variant.zip" "$env:GITHUB_WORKSPACE\out\$variant\*"
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
dotnet restore ArchiSteamFarm
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($variant in $env:VARIANTS.Split([char[]] $null, [System.StringSplitOptions]::RemoveEmptyEntries)) {
|
||||
@@ -173,44 +182,51 @@ jobs:
|
||||
Get-Job | Receive-Job -Wait -AutoRemoveJob
|
||||
|
||||
- name: Upload ASF-generic
|
||||
uses: actions/upload-artifact@v1
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-generic
|
||||
path: out/generic
|
||||
path: out/ASF-generic.zip
|
||||
|
||||
- name: Upload ASF-generic-netf
|
||||
continue-on-error: true
|
||||
if: startsWith(matrix.os, 'windows-')
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-generic-netf
|
||||
path: out/generic-netf
|
||||
path: out/ASF-generic-netf.zip
|
||||
|
||||
- name: Upload ASF-linux-arm
|
||||
uses: actions/upload-artifact@v1
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-linux-arm
|
||||
path: out/linux-arm
|
||||
path: out/ASF-linux-arm.zip
|
||||
|
||||
- name: Upload ASF-linux-arm64
|
||||
uses: actions/upload-artifact@v1
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-linux-arm64
|
||||
path: out/linux-arm64
|
||||
path: out/ASF-linux-arm64.zip
|
||||
|
||||
- name: Upload ASF-linux-x64
|
||||
uses: actions/upload-artifact@v1
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-linux-x64
|
||||
path: out/linux-x64
|
||||
path: out/ASF-linux-x64.zip
|
||||
|
||||
- name: Upload ASF-osx-x64
|
||||
uses: actions/upload-artifact@v1
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-osx-x64
|
||||
path: out/osx-x64
|
||||
path: out/ASF-osx-x64.zip
|
||||
|
||||
- name: Upload ASF-win-x64
|
||||
uses: actions/upload-artifact@v1
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-win-x64
|
||||
path: out/win-x64
|
||||
path: out/ASF-win-x64.zip
|
||||
|
||||
36
.travis.yml
36
.travis.yml
@@ -17,6 +17,9 @@ env:
|
||||
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
|
||||
- NET_CORE_VERSION: netcoreapp3.1
|
||||
- VARIANTS="generic linux-arm linux-arm64 linux-x64 osx-x64 win-x64" # NOTE: When modifying variants, don't forget to update ASF_VARIANT definitions in SharedInfo.cs!
|
||||
addons:
|
||||
homebrew:
|
||||
packages: p7zip
|
||||
before_script:
|
||||
- |
|
||||
set -eu
|
||||
@@ -31,15 +34,18 @@ script:
|
||||
npm ci --no-progress --prefix ASF-ui
|
||||
npm run-script deploy --no-progress --prefix ASF-ui
|
||||
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" --nologo
|
||||
dotnet build ArchiSteamFarm.CustomPlugins.ExamplePlugin -c "$CONFIGURATION" -f "$NET_CORE_VERSION" --nologo
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -f "$NET_CORE_VERSION" --nologo
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:UseAppHost=false --nologo
|
||||
dotnet build ArchiSteamFarm.CustomPlugins.ExamplePlugin -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:UseAppHost=false --nologo
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:UseAppHost=false --nologo
|
||||
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:UseAppHost=false --nologo
|
||||
dotnet restore ArchiSteamFarm
|
||||
|
||||
publish() {
|
||||
if [ "$1" = 'generic' ]; then
|
||||
local variantArgs="-p:UseAppHost=false"
|
||||
else
|
||||
local variantArgs="-p:PublishTrimmed=true -r $1"
|
||||
local variantArgs="-p:PublishSingleFile=true -p:PublishTrimmed=true -r $1"
|
||||
fi
|
||||
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o "out/${1}" "-p:ASFVariant=$1" --no-restore --nologo $variantArgs
|
||||
@@ -48,10 +54,22 @@ script:
|
||||
if [ -d "ArchiSteamFarm/overlay/${1}" ]; then
|
||||
cp "ArchiSteamFarm/overlay/${1}/"* "out/${1}"
|
||||
fi
|
||||
}
|
||||
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" --nologo
|
||||
dotnet restore ArchiSteamFarm
|
||||
# Include .ico file for all platforms, since only Windows script can bundle it inside the exe
|
||||
cp "resources/ASF.ico" "out/${1}/ArchiSteamFarm.ico"
|
||||
|
||||
if command -v 7z >/dev/null; then
|
||||
7z a -bd -slp -tzip -mm=Deflate -mx=1 "out/ASF-${1}.zip" "${TRAVIS_BUILD_DIR}/out/${1}/*"
|
||||
elif command -v zip >/dev/null; then
|
||||
(
|
||||
cd "${TRAVIS_BUILD_DIR}/out/${1}"
|
||||
zip -1 -q -r "../ASF-${1}.zip" .
|
||||
)
|
||||
else
|
||||
echo "ERROR: No supported zip tool!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
jobs=""
|
||||
|
||||
@@ -76,5 +94,5 @@ matrix:
|
||||
dist: bionic
|
||||
- os: osx
|
||||
# Ref: https://docs.travis-ci.com/user/reference/osx
|
||||
dotnet: 3.1.201 # For OSX, we need absolute dotnet version until https://github.com/dotnet/core-setup/issues/4187 is resolved
|
||||
osx_image: xcode11.3
|
||||
dotnet: 3.1.300 # For OSX, we need absolute dotnet version until https://github.com/dotnet/core-setup/issues/4187 is resolved
|
||||
osx_image: xcode11.4
|
||||
|
||||
Submodule ASF-WebConfigGenerator updated: b74c5774cd...296f453fd3
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: c6df93f63c...dc5f0b139f
@@ -92,7 +92,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=EnforceWhileStatementBraces/@EntryIndexedValue">WARNING</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=FunctionComplexityOverflow/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=FunctionComplexityOverflow/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ImplicitlyCapturedClosure/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=IncorrectBlankLinesNearBraces/@EntryIndexedValue">WARNING</s:String>
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.IPC;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.NLog;
|
||||
@@ -55,6 +58,13 @@ namespace ArchiSteamFarm {
|
||||
[PublicAPI]
|
||||
public static WebBrowser WebBrowser { get; internal set; }
|
||||
|
||||
internal static ICrossProcessSemaphore ConfirmationsSemaphore { get; private set; }
|
||||
internal static ICrossProcessSemaphore GiftsSemaphore { get; private set; }
|
||||
internal static ICrossProcessSemaphore InventorySemaphore { get; private set; }
|
||||
internal static ICrossProcessSemaphore LoginRateLimitingSemaphore { get; private set; }
|
||||
internal static ICrossProcessSemaphore LoginSemaphore { get; private set; }
|
||||
internal static ImmutableDictionary<string, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)> WebLimitingSemaphores { get; private set; }
|
||||
|
||||
private static readonly SemaphoreSlim UpdateSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private static Timer AutoUpdatesTimer;
|
||||
@@ -131,6 +141,22 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
GlobalConfig = globalConfig;
|
||||
|
||||
string webProxyText = !string.IsNullOrEmpty(globalConfig.WebProxyText) ? "-" + BitConverter.ToString(Encoding.UTF8.GetBytes(globalConfig.WebProxyText)).Replace("-", "") : "";
|
||||
|
||||
ConfirmationsSemaphore ??= OS.CreateCrossProcessSemaphore(nameof(ConfirmationsSemaphore) + webProxyText);
|
||||
GiftsSemaphore ??= OS.CreateCrossProcessSemaphore(nameof(GiftsSemaphore) + webProxyText);
|
||||
InventorySemaphore ??= OS.CreateCrossProcessSemaphore(nameof(InventorySemaphore) + webProxyText);
|
||||
LoginRateLimitingSemaphore ??= OS.CreateCrossProcessSemaphore(nameof(LoginRateLimitingSemaphore) + webProxyText);
|
||||
LoginSemaphore ??= OS.CreateCrossProcessSemaphore(nameof(LoginSemaphore) + webProxyText);
|
||||
|
||||
WebLimitingSemaphores ??= new Dictionary<string, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>(4, StringComparer.OrdinalIgnoreCase) {
|
||||
{ nameof(ArchiWebHandler), (OS.CreateCrossProcessSemaphore(nameof(ArchiWebHandler) + webProxyText), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ ArchiWebHandler.SteamCommunityURL, (OS.CreateCrossProcessSemaphore(nameof(ArchiWebHandler) + webProxyText + "-" + nameof(ArchiWebHandler.SteamCommunityURL)), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ ArchiWebHandler.SteamHelpURL, (OS.CreateCrossProcessSemaphore(nameof(ArchiWebHandler) + webProxyText + "-" + nameof(ArchiWebHandler.SteamHelpURL)), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ ArchiWebHandler.SteamStoreURL, (OS.CreateCrossProcessSemaphore(nameof(ArchiWebHandler) + webProxyText + "-" + nameof(ArchiWebHandler.SteamStoreURL)), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ WebAPI.DefaultBaseAddress.Host, (OS.CreateCrossProcessSemaphore(nameof(ArchiWebHandler) + webProxyText + "-" + nameof(WebAPI)), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }
|
||||
}.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
internal static void InitGlobalDatabase(GlobalDatabase globalDatabase) {
|
||||
@@ -279,7 +305,7 @@ namespace ArchiSteamFarm {
|
||||
string executable = Path.Combine(SharedInfo.HomeDirectory, SharedInfo.AssemblyName);
|
||||
|
||||
if (File.Exists(executable)) {
|
||||
OS.UnixSetFileAccessExecutable(executable);
|
||||
OS.UnixSetFileAccess(executable, OS.EUnixPermission.Combined755);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,8 +683,10 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
await SteamDirectory.LoadAsync(steamConfiguration).ConfigureAwait(false);
|
||||
ArchiLogger.LogGenericInfo(Strings.Success);
|
||||
} catch {
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
ArchiLogger.LogGenericWarning(Strings.BotSteamDirectoryInitializationFailed);
|
||||
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -751,8 +779,8 @@ namespace ArchiSteamFarm {
|
||||
case "":
|
||||
// No directory, root folder
|
||||
switch (fileName) {
|
||||
case Logging.NLogConfigurationFile:
|
||||
case SharedInfo.LogFile:
|
||||
case "NLog.config":
|
||||
// Files with those names in root directory we want to keep
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
@@ -36,7 +35,6 @@ using SteamKit2;
|
||||
namespace ArchiSteamFarm {
|
||||
public sealed class Actions : IAsyncDisposable {
|
||||
private static readonly SemaphoreSlim GiftCardsSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
|
||||
@@ -46,7 +44,7 @@ namespace ArchiSteamFarm {
|
||||
private bool ProcessingGiftsScheduled;
|
||||
private bool TradingScheduled;
|
||||
|
||||
internal Actions([JetBrains.Annotations.NotNull] Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
internal Actions([NotNull] Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
|
||||
public async ValueTask DisposeAsync() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
@@ -236,7 +234,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
public async Task<(bool Success, string Message)> SendInventory(uint appID = Steam.Asset.SteamAppID, ulong contextID = Steam.Asset.SteamCommunityContextID, ulong targetSteamID = 0, string tradeToken = null, Func<Steam.Asset, bool> filterFunction = null) {
|
||||
if ((appID == 0) || (contextID == 0)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(contextID));
|
||||
@@ -440,16 +437,22 @@ namespace ArchiSteamFarm {
|
||||
private ulong GetFirstSteamMasterID() => Bot.BotConfig.SteamUserPermissions.Where(kv => (kv.Key != 0) && (kv.Value == BotConfig.EPermission.Master)).Select(kv => kv.Key).OrderByDescending(steamID => steamID != Bot.SteamID).ThenBy(steamID => steamID).FirstOrDefault();
|
||||
|
||||
private static async Task LimitGiftsRequestsAsync() {
|
||||
if (ASF.GiftsSemaphore == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(ASF.GiftsSemaphore));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ASF.GlobalConfig.GiftsLimiterDelay == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await GiftsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await ASF.GiftsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
Utilities.InBackground(
|
||||
async () => {
|
||||
await Task.Delay(ASF.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false);
|
||||
GiftsSemaphore.Release();
|
||||
ASF.GiftsSemaphore.Release();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>../resources/ASF.ico</ApplicationIcon>
|
||||
<AssemblyVersion>4.2.0.7</AssemblyVersion>
|
||||
<AssemblyVersion>4.2.1.2</AssemblyVersion>
|
||||
<Authors>JustArchi</Authors>
|
||||
<Company>JustArchi</Company>
|
||||
<Copyright>Copyright © ArchiSteamFarm 2015-2020</Copyright>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);config/**;debug/**;out/**;overlay/**</DefaultItemExcludes>
|
||||
<Description>ASF is a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously.</Description>
|
||||
<ErrorReport>none</ErrorReport>
|
||||
<FileVersion>4.2.0.7</FileVersion>
|
||||
<FileVersion>4.2.1.2</FileVersion>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<NoWarn>1591</NoWarn>
|
||||
@@ -62,18 +62,19 @@
|
||||
<PackageReference Include="Markdig.Signed" Version="0.20.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Nito.AsyncEx.Coordination" Version="5.0.0" />
|
||||
<PackageReference Include="NLog" Version="4.7.0" />
|
||||
<PackageReference Include="NLog" Version="4.7.2" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.2" />
|
||||
<PackageReference Include="SteamKit2" Version="2.3.0-Beta.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.4.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.4.1" />
|
||||
<PackageReference Include="System.Composition" Version="1.4.0" />
|
||||
<PackageReference Include="System.Composition" Version="1.4.1" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.7.0" />
|
||||
<PackageReference Include="System.Linq.Async" Version="4.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -86,9 +87,9 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.1.3" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="1.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.1.4" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="1.7.1" />
|
||||
<Reference Include="System.Net.Http">
|
||||
<HintPath>C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Net.Http.dll</HintPath>
|
||||
</Reference>
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -62,16 +61,6 @@ namespace ArchiSteamFarm {
|
||||
private const string SteamHelpHost = "help.steampowered.com";
|
||||
private const string SteamStoreHost = "store.steampowered.com";
|
||||
|
||||
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private static readonly ImmutableDictionary<string, (SemaphoreSlim RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)> WebLimitingSemaphores = new Dictionary<string, (SemaphoreSlim RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>(4, StringComparer.Ordinal) {
|
||||
{ nameof(ArchiWebHandler), (new SemaphoreSlim(1, 1), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ SteamCommunityURL, (new SemaphoreSlim(1, 1), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ SteamHelpURL, (new SemaphoreSlim(1, 1), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ SteamStoreURL, (new SemaphoreSlim(1, 1), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ WebAPI.DefaultBaseAddress.Host, (new SemaphoreSlim(1, 1), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }
|
||||
}.ToImmutableDictionary(StringComparer.Ordinal);
|
||||
|
||||
[PublicAPI]
|
||||
public readonly ArchiCacheable<string> CachedApiKey;
|
||||
|
||||
@@ -88,7 +77,7 @@ namespace ArchiSteamFarm {
|
||||
private bool MarkingInventoryScheduled;
|
||||
private string VanityURL;
|
||||
|
||||
internal ArchiWebHandler([JetBrains.Annotations.NotNull] Bot bot) {
|
||||
internal ArchiWebHandler([NotNull] Bot bot) {
|
||||
Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
|
||||
CachedApiKey = new ArchiCacheable<string>(ResolveApiKey);
|
||||
@@ -121,14 +110,17 @@ namespace ArchiSteamFarm {
|
||||
return string.IsNullOrEmpty(VanityURL) ? "/profiles/" + Bot.SteamID : "/id/" + VanityURL;
|
||||
}
|
||||
|
||||
[JetBrains.Annotations.NotNull]
|
||||
[NotNull]
|
||||
[PublicAPI]
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
public async IAsyncEnumerable<Steam.Asset> GetInventoryAsync(ulong steamID = 0, uint appID = Steam.Asset.SteamAppID, ulong contextID = Steam.Asset.SteamCommunityContextID) {
|
||||
if ((appID == 0) || (contextID == 0)) {
|
||||
throw new ArgumentException(string.Format(Strings.ErrorObjectIsNull, nameof(appID) + " || " + nameof(contextID)));
|
||||
}
|
||||
|
||||
if (ASF.InventorySemaphore == null) {
|
||||
throw new ArgumentNullException(nameof(ASF.InventorySemaphore));
|
||||
}
|
||||
|
||||
if (steamID == 0) {
|
||||
if (!Initialized) {
|
||||
for (byte i = 0; (i < ASF.GlobalConfig.ConnectionTimeout) && !Initialized && Bot.IsConnectedAndLoggedOn; i++) {
|
||||
@@ -152,7 +144,7 @@ namespace ArchiSteamFarm {
|
||||
HashSet<ulong> assetIDs = new HashSet<ulong>();
|
||||
|
||||
while (true) {
|
||||
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await ASF.InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
Steam.InventoryResponse response = await UrlGetToJsonObjectWithSession<Steam.InventoryResponse>(SteamCommunityURL, request + (startAssetID > 0 ? "&start_assetid=" + startAssetID : "")).ConfigureAwait(false);
|
||||
@@ -222,12 +214,12 @@ namespace ArchiSteamFarm {
|
||||
startAssetID = response.LastAssetID;
|
||||
} finally {
|
||||
if (ASF.GlobalConfig.InventoryLimiterDelay == 0) {
|
||||
InventorySemaphore.Release();
|
||||
ASF.InventorySemaphore.Release();
|
||||
} else {
|
||||
Utilities.InBackground(
|
||||
async () => {
|
||||
await Task.Delay(ASF.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
|
||||
InventorySemaphore.Release();
|
||||
ASF.InventorySemaphore.Release();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1183,14 +1175,20 @@ namespace ArchiSteamFarm {
|
||||
return default;
|
||||
}
|
||||
|
||||
if (ASF.WebLimitingSemaphores == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(ASF.WebLimitingSemaphores));
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
if (ASF.GlobalConfig.WebLimiterDelay == 0) {
|
||||
return await function().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!WebLimitingSemaphores.TryGetValue(service, out (SemaphoreSlim RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore) limiters)) {
|
||||
if (!ASF.WebLimitingSemaphores.TryGetValue(service, out (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore) limiters)) {
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(service), service));
|
||||
|
||||
if (!WebLimitingSemaphores.TryGetValue(nameof(ArchiWebHandler), out limiters)) {
|
||||
if (!ASF.WebLimitingSemaphores.TryGetValue(nameof(ArchiWebHandler), out limiters)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(limiters));
|
||||
|
||||
return await function().ConfigureAwait(false);
|
||||
@@ -1381,7 +1379,7 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
[JetBrains.Annotations.NotNull]
|
||||
[NotNull]
|
||||
internal HttpClient GenerateDisposableHttpClient() => WebBrowser.GenerateDisposableHttpClient();
|
||||
|
||||
[ItemCanBeNull]
|
||||
@@ -2155,9 +2153,15 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal async Task MarkInventory() {
|
||||
if (ASF.InventorySemaphore == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(ASF.InventorySemaphore));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We aim to have a maximum of 2 tasks, one already working, and one waiting in the queue
|
||||
// This way we can call this function as many times as needed e.g. because of Steam events
|
||||
lock (InventorySemaphore) {
|
||||
lock (ASF.InventorySemaphore) {
|
||||
if (MarkingInventoryScheduled) {
|
||||
return;
|
||||
}
|
||||
@@ -2165,10 +2169,10 @@ namespace ArchiSteamFarm {
|
||||
MarkingInventoryScheduled = true;
|
||||
}
|
||||
|
||||
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await ASF.InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
lock (InventorySemaphore) {
|
||||
lock (ASF.InventorySemaphore) {
|
||||
MarkingInventoryScheduled = false;
|
||||
}
|
||||
|
||||
@@ -2176,12 +2180,12 @@ namespace ArchiSteamFarm {
|
||||
await UrlHeadWithSession(SteamCommunityURL, request, false).ConfigureAwait(false);
|
||||
} finally {
|
||||
if (ASF.GlobalConfig.InventoryLimiterDelay == 0) {
|
||||
InventorySemaphore.Release();
|
||||
ASF.InventorySemaphore.Release();
|
||||
} else {
|
||||
Utilities.InBackground(
|
||||
async () => {
|
||||
await Task.Delay(ASF.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
|
||||
InventorySemaphore.Release();
|
||||
ASF.InventorySemaphore.Release();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
@@ -62,8 +61,6 @@ namespace ArchiSteamFarm {
|
||||
internal static EOSType OSType { get; private set; } = EOSType.Unknown;
|
||||
|
||||
private static readonly SemaphoreSlim BotsSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SemaphoreSlim LoginRateLimitingSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
[JsonIgnore]
|
||||
[PublicAPI]
|
||||
@@ -151,7 +148,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
#pragma warning disable IDE0051
|
||||
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamID))]
|
||||
[JetBrains.Annotations.NotNull]
|
||||
[NotNull]
|
||||
private string SSteamID => SteamID.ToString();
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
@@ -203,7 +200,7 @@ namespace ArchiSteamFarm {
|
||||
private string TwoFactorCode;
|
||||
private byte TwoFactorCodeFailures;
|
||||
|
||||
private Bot([JetBrains.Annotations.NotNull] string botName, [JetBrains.Annotations.NotNull] BotConfig botConfig, [JetBrains.Annotations.NotNull] BotDatabase botDatabase) {
|
||||
private Bot([NotNull] string botName, [NotNull] BotConfig botConfig, [NotNull] BotDatabase botDatabase) {
|
||||
if (string.IsNullOrEmpty(botName) || (botConfig == null) || (botDatabase == null)) {
|
||||
throw new ArgumentNullException(nameof(botName) + " || " + nameof(botConfig) + " || " + nameof(botDatabase));
|
||||
}
|
||||
@@ -1913,7 +1910,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private async Task JoinMasterChatGroupID() {
|
||||
if (BotConfig.SteamMasterClanID == 0) {
|
||||
if ((BotConfig.SteamMasterClanID == 0) || IsAccountLimited) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1939,23 +1936,29 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static async Task LimitLoginRequestsAsync() {
|
||||
if (ASF.GlobalConfig.LoginLimiterDelay == 0) {
|
||||
await LoginRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
LoginRateLimitingSemaphore.Release();
|
||||
if ((ASF.LoginSemaphore == null) || (ASF.LoginRateLimitingSemaphore == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(ASF.LoginSemaphore) + " || " + nameof(ASF.LoginRateLimitingSemaphore));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await LoginSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
if (ASF.GlobalConfig.LoginLimiterDelay == 0) {
|
||||
await ASF.LoginRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
ASF.LoginRateLimitingSemaphore.Release();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await ASF.LoginSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
await LoginRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
LoginRateLimitingSemaphore.Release();
|
||||
await ASF.LoginRateLimitingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
ASF.LoginRateLimitingSemaphore.Release();
|
||||
} finally {
|
||||
Utilities.InBackground(
|
||||
async () => {
|
||||
await Task.Delay(ASF.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
|
||||
LoginSemaphore.Release();
|
||||
ASF.LoginSemaphore.Release();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2074,6 +2077,12 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ASF.LoginRateLimitingSemaphore == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(ASF.LoginRateLimitingSemaphore));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
EResult lastLogOnResult = LastLogOnResult;
|
||||
LastLogOnResult = EResult.Invalid;
|
||||
HeartBeatFailures = 0;
|
||||
@@ -2120,14 +2129,14 @@ namespace ArchiSteamFarm {
|
||||
case EResult.RateLimitExceeded:
|
||||
ArchiLogger.LogGenericInfo(string.Format(Strings.BotRateLimitExceeded, TimeSpan.FromMinutes(LoginCooldownInMinutes).ToHumanReadable()));
|
||||
|
||||
if (!await LoginRateLimitingSemaphore.WaitAsync(1000 * WebBrowser.MaxTries).ConfigureAwait(false)) {
|
||||
if (!await ASF.LoginRateLimitingSemaphore.WaitAsync(1000 * WebBrowser.MaxTries).ConfigureAwait(false)) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
await Task.Delay(LoginCooldownInMinutes * 60 * 1000).ConfigureAwait(false);
|
||||
} finally {
|
||||
LoginRateLimitingSemaphore.Release();
|
||||
ASF.LoginRateLimitingSemaphore.Release();
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -2820,7 +2829,6 @@ namespace ArchiSteamFarm {
|
||||
WalletCurrency = callback.Currency;
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
private async Task RedeemGamesInBackground() {
|
||||
if (!await GamesRedeemerInBackgroundSemaphore.WaitAsync(0).ConfigureAwait(false)) {
|
||||
return;
|
||||
|
||||
@@ -23,7 +23,6 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -98,7 +97,7 @@ namespace ArchiSteamFarm {
|
||||
private bool PermanentlyPaused;
|
||||
private bool ShouldResumeFarming = true;
|
||||
|
||||
internal CardsFarmer([JetBrains.Annotations.NotNull] Bot bot) {
|
||||
internal CardsFarmer([NotNull] Bot bot) {
|
||||
Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
|
||||
if (ASF.GlobalConfig.IdleFarmingPeriod > 0) {
|
||||
@@ -373,7 +372,6 @@ namespace ArchiSteamFarm {
|
||||
await StartFarming().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
private async Task CheckPage(IDocument htmlDocument, ISet<uint> parsedAppIDs) {
|
||||
if ((htmlDocument == null) || (parsedAppIDs == null)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(htmlDocument) + " || " + nameof(parsedAppIDs));
|
||||
@@ -1243,7 +1241,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal uint PlayableAppID { get; set; }
|
||||
|
||||
internal Game(uint appID, [JetBrains.Annotations.NotNull] string gameName, float hoursPlayed, ushort cardsRemaining, byte badgeLevel) {
|
||||
internal Game(uint appID, [NotNull] string gameName, float hoursPlayed, ushort cardsRemaining, byte badgeLevel) {
|
||||
if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
|
||||
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -40,7 +39,7 @@ namespace ArchiSteamFarm {
|
||||
private readonly Bot Bot;
|
||||
private readonly Dictionary<uint, string> CachedGamesOwned = new Dictionary<uint, string>();
|
||||
|
||||
internal Commands([JetBrains.Annotations.NotNull] Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
internal Commands([NotNull] Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
|
||||
[PublicAPI]
|
||||
public static string FormatBotResponse(string response, string botName) {
|
||||
@@ -1635,7 +1634,6 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
private async Task<(string Response, Dictionary<string, string> OwnedGames)> ResponseOwns(ulong steamID, string query) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(query)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query));
|
||||
@@ -2154,7 +2152,6 @@ namespace ArchiSteamFarm {
|
||||
return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
private async Task<string> ResponseRedeem(ulong steamID, string keysText, ERedeemFlags redeemFlags = ERedeemFlags.None) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(keysText)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(keysText));
|
||||
|
||||
199
ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs
Normal file
199
ArchiSteamFarm/Helpers/CrossProcessFileBasedSemaphore.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.AccessControl;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers {
|
||||
internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore {
|
||||
private const ushort SpinLockDelay = 1000; // In milliseconds
|
||||
|
||||
private readonly string FilePath;
|
||||
private readonly SemaphoreSlim LocalSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private FileStream FileLock;
|
||||
|
||||
internal CrossProcessFileBasedSemaphore([NotNull] string name) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
FilePath = Path.Combine(Path.GetTempPath(), SharedInfo.ASF, name);
|
||||
|
||||
EnsureFileExists();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
LocalSemaphore.Dispose();
|
||||
|
||||
FileLock?.Dispose();
|
||||
}
|
||||
|
||||
void ICrossProcessSemaphore.Release() {
|
||||
lock (LocalSemaphore) {
|
||||
if (FileLock == null) {
|
||||
throw new ArgumentNullException(nameof(FileLock));
|
||||
}
|
||||
|
||||
FileLock.Dispose();
|
||||
FileLock = null;
|
||||
}
|
||||
|
||||
LocalSemaphore.Release();
|
||||
}
|
||||
|
||||
async Task ICrossProcessSemaphore.WaitAsync() {
|
||||
await LocalSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
bool success = false;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
try {
|
||||
lock (LocalSemaphore) {
|
||||
if (FileLock != null) {
|
||||
throw new ArgumentNullException(nameof(FileLock));
|
||||
}
|
||||
|
||||
EnsureFileExists();
|
||||
|
||||
FileLock = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None);
|
||||
success = true;
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (IOException) {
|
||||
await Task.Delay(SpinLockDelay).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!success) {
|
||||
LocalSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task<bool> ICrossProcessSemaphore.WaitAsync(int millisecondsTimeout) {
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
|
||||
if (!await LocalSemaphore.WaitAsync(millisecondsTimeout).ConfigureAwait(false)) {
|
||||
stopwatch.Stop();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
bool success = false;
|
||||
|
||||
try {
|
||||
millisecondsTimeout -= (int) stopwatch.ElapsedMilliseconds;
|
||||
|
||||
if (millisecondsTimeout <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
try {
|
||||
lock (LocalSemaphore) {
|
||||
if (FileLock != null) {
|
||||
throw new ArgumentNullException(nameof(FileLock));
|
||||
}
|
||||
|
||||
EnsureFileExists();
|
||||
|
||||
FileLock = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None);
|
||||
success = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (IOException) {
|
||||
if (millisecondsTimeout <= SpinLockDelay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await Task.Delay(SpinLockDelay).ConfigureAwait(false);
|
||||
millisecondsTimeout -= SpinLockDelay;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!success) {
|
||||
LocalSemaphore.Release();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!success) {
|
||||
LocalSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureFileExists() {
|
||||
FileInfo fileInfo = new FileInfo(FilePath);
|
||||
|
||||
if (fileInfo.Exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
string directoryPath = Path.GetDirectoryName(FilePath);
|
||||
|
||||
if (string.IsNullOrEmpty(directoryPath)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(directoryPath));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath);
|
||||
|
||||
if (!directoryInfo.Exists) {
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
|
||||
if (OS.IsUnix) {
|
||||
OS.UnixSetFileAccess(directoryPath, OS.EUnixPermission.Combined777);
|
||||
} else {
|
||||
DirectorySecurity directorySecurity = new DirectorySecurity(FilePath, AccessControlSections.All);
|
||||
|
||||
directoryInfo.SetAccessControl(directorySecurity);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
using (new FileStream(FilePath, FileMode.CreateNew)) { }
|
||||
|
||||
if (OS.IsUnix) {
|
||||
OS.UnixSetFileAccess(FilePath, OS.EUnixPermission.Combined777);
|
||||
} else {
|
||||
FileSecurity fileSecurity = new FileSecurity(FilePath, AccessControlSections.All);
|
||||
|
||||
fileInfo.SetAccessControl(fileSecurity);
|
||||
}
|
||||
} catch (IOException) {
|
||||
// Ignored, if the file was already created in the meantime by another instance, this is fine
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
ArchiSteamFarm/Helpers/CrossProcessSemaphore.cs
Normal file
53
ArchiSteamFarm/Helpers/CrossProcessSemaphore.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers {
|
||||
internal sealed class CrossProcessSemaphore : ICrossProcessSemaphore {
|
||||
private readonly Semaphore GlobalSemaphore;
|
||||
|
||||
internal CrossProcessSemaphore([NotNull] string name) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
GlobalSemaphore = new Semaphore(1, 1, name);
|
||||
}
|
||||
|
||||
public void Dispose() => GlobalSemaphore.Dispose();
|
||||
|
||||
void ICrossProcessSemaphore.Release() => GlobalSemaphore.Release();
|
||||
|
||||
[NotNull]
|
||||
Task ICrossProcessSemaphore.WaitAsync() {
|
||||
GlobalSemaphore.WaitOne();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[NotNull]
|
||||
Task<bool> ICrossProcessSemaphore.WaitAsync(int millisecondsTimeout) => Task.FromResult(GlobalSemaphore.WaitOne(millisecondsTimeout));
|
||||
}
|
||||
}
|
||||
31
ArchiSteamFarm/Helpers/ICrossProcessSemaphore.cs
Normal file
31
ArchiSteamFarm/Helpers/ICrossProcessSemaphore.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2020 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers {
|
||||
internal interface ICrossProcessSemaphore : IDisposable {
|
||||
internal void Release();
|
||||
internal Task WaitAsync();
|
||||
internal Task<bool> WaitAsync(int millisecondsTimeout);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ namespace ArchiSteamFarm {
|
||||
private const byte SteamTimeTTL = 24; // For how many hours we can assume that SteamTimeDifference is correct
|
||||
|
||||
private static readonly char[] CodeCharacters = { '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y' };
|
||||
private static readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1, 1);
|
||||
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private static DateTime LastSteamTimeCheck;
|
||||
@@ -439,16 +438,22 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private static async Task LimitConfirmationsRequestsAsync() {
|
||||
if (ASF.ConfirmationsSemaphore == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(ASF.ConfirmationsSemaphore));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ASF.GlobalConfig.ConfirmationsLimiterDelay == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await ASF.ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
Utilities.InBackground(
|
||||
async () => {
|
||||
await Task.Delay(ASF.GlobalConfig.ConfirmationsLimiterDelay * 1000).ConfigureAwait(false);
|
||||
ConfirmationsSemaphore.Release();
|
||||
ASF.ConfirmationsSemaphore.Release();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ using NLog.Targets;
|
||||
|
||||
namespace ArchiSteamFarm.NLog {
|
||||
internal static class Logging {
|
||||
private const byte ConsoleResponsivenessDelay = 250; // In miliseconds
|
||||
internal const string NLogConfigurationFile = "NLog.config";
|
||||
|
||||
private const byte ConsoleResponsivenessDelay = 250; // In milliseconds
|
||||
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${processname}-${processid}|${level:uppercase=true}|" + LayoutMessage;
|
||||
private const string LayoutMessage = @"${logger}|${message}${onexception:inner= ${exception:format=toString,Data}}";
|
||||
|
||||
@@ -142,8 +144,8 @@ namespace ArchiSteamFarm.NLog {
|
||||
|
||||
internal static void InitCoreLoggers(bool uniqueInstance) {
|
||||
try {
|
||||
if ((Directory.GetCurrentDirectory() != SharedInfo.HomeDirectory) && File.Exists("NLog.config")) {
|
||||
LogManager.Configuration = new XmlLoggingConfiguration("NLog.config");
|
||||
if ((Directory.GetCurrentDirectory() != AppContext.BaseDirectory) && File.Exists(NLogConfigurationFile)) {
|
||||
LogManager.Configuration = new XmlLoggingConfiguration(NLogConfigurationFile);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -23,9 +23,11 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
@@ -60,6 +62,25 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal static ICrossProcessSemaphore CreateCrossProcessSemaphore(string objectName) {
|
||||
if (string.IsNullOrEmpty(objectName)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(objectName));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
string resourceName = GetOsResourceName(objectName);
|
||||
|
||||
try {
|
||||
return new CrossProcessSemaphore(resourceName);
|
||||
} catch (PlatformNotSupportedException e) {
|
||||
// CrossProcessSemaphore is currently available only for Windows platforms, we use alternative synchronization for other OSes
|
||||
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
return new CrossProcessFileBasedSemaphore(resourceName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Init(bool systemRequired, GlobalConfig.EOptimizationMode optimizationMode) {
|
||||
if (!Enum.IsDefined(typeof(GlobalConfig.EOptimizationMode), optimizationMode)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(optimizationMode));
|
||||
@@ -94,7 +115,14 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
string uniqueName = "Global\\" + SharedInfo.AssemblyName + "-" + Convert.ToBase64String(Encoding.UTF8.GetBytes(Directory.GetCurrentDirectory()));
|
||||
string uniqueName;
|
||||
|
||||
// The only purpose of using hashingAlgorithm here is to cut on a potential size of the resource name - paths can be really long, and we almost certainly have some upper limit on the resource name we can allocate
|
||||
// At the same time it'd be the best if we avoided all special characters, such as '/' found e.g. in base64, as we can't be sure that it's not a prohibited character in regards to native OS implementation
|
||||
// Because of that, MD5 is sufficient for our case, as it generates alphanumeric characters only, and is barely 128-bit long. We don't need any kind of complex cryptography or collision detection here, any hashing algorithm will do, and the shorter the better
|
||||
using (MD5 hashingAlgorithm = MD5.Create()) {
|
||||
uniqueName = "Global\\" + GetOsResourceName(nameof(SingleInstance)) + "-" + BitConverter.ToString(hashingAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(Directory.GetCurrentDirectory()))).Replace("-", "");
|
||||
}
|
||||
|
||||
Mutex singleInstance = new Mutex(true, uniqueName, out bool result);
|
||||
|
||||
@@ -109,7 +137,7 @@ namespace ArchiSteamFarm {
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void UnixSetFileAccessExecutable(string path) {
|
||||
internal static void UnixSetFileAccess(string path, EUnixPermission permission) {
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(path));
|
||||
|
||||
@@ -121,7 +149,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Chmod() returns 0 on success, -1 on failure
|
||||
if (NativeMethods.Chmod(path, (int) NativeMethods.UnixExecutePermission) != 0) {
|
||||
if (NativeMethods.Chmod(path, (int) permission) != 0) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Marshal.GetLastWin32Error()));
|
||||
}
|
||||
}
|
||||
@@ -137,6 +165,16 @@ namespace ArchiSteamFarm {
|
||||
SingleInstance = null;
|
||||
}
|
||||
|
||||
private static string GetOsResourceName(string objectName) {
|
||||
if (string.IsNullOrEmpty(objectName)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(objectName));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return SharedInfo.AssemblyName + "-" + objectName;
|
||||
}
|
||||
|
||||
private static void WindowsDisableQuickEditMode() {
|
||||
if (!IsWindows) {
|
||||
return;
|
||||
@@ -173,11 +211,25 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum EUnixPermission : ushort {
|
||||
OtherExecute = 0x1,
|
||||
OtherWrite = 0x2,
|
||||
OtherRead = 0x4,
|
||||
GroupExecute = 0x8,
|
||||
GroupWrite = 0x10,
|
||||
GroupRead = 0x20,
|
||||
UserExecute = 0x40,
|
||||
UserWrite = 0x80,
|
||||
UserRead = 0x100,
|
||||
Combined755 = UserRead | UserWrite | UserExecute | GroupRead | GroupExecute | OtherRead | OtherExecute,
|
||||
Combined777 = UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute
|
||||
}
|
||||
|
||||
private static class NativeMethods {
|
||||
internal const EExecutionState AwakeExecutionState = EExecutionState.SystemRequired | EExecutionState.AwayModeRequired | EExecutionState.Continuous;
|
||||
internal const uint EnableQuickEditMode = 0x0040;
|
||||
internal const sbyte StandardInputHandle = -10;
|
||||
internal const EUnixPermission UnixExecutePermission = EUnixPermission.UserRead | EUnixPermission.UserWrite | EUnixPermission.UserExecute | EUnixPermission.GroupRead | EUnixPermission.GroupExecute | EUnixPermission.OtherRead | EUnixPermission.OtherExecute;
|
||||
|
||||
[DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
|
||||
internal static extern int Chmod(string path, int mode);
|
||||
@@ -201,22 +253,6 @@ namespace ArchiSteamFarm {
|
||||
AwayModeRequired = 0x00000040,
|
||||
Continuous = 0x80000000
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum EUnixPermission : ushort {
|
||||
OtherExecute = 0x1,
|
||||
OtherRead = 0x4,
|
||||
GroupExecute = 0x8,
|
||||
GroupRead = 0x20,
|
||||
UserExecute = 0x40,
|
||||
UserWrite = 0x80,
|
||||
UserRead = 0x100
|
||||
|
||||
/*
|
||||
OtherWrite = 0x2
|
||||
GroupWrite = 0x10
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,13 +217,18 @@ namespace ArchiSteamFarm {
|
||||
// GetCultureInfo() would be better but we can't use it for specifying neutral cultures such as "en"
|
||||
CultureInfo culture = CultureInfo.CreateSpecificCulture(ASF.GlobalConfig.CurrentCulture);
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = culture;
|
||||
} catch (Exception) {
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
ASF.ArchiLogger.LogGenericError(Strings.ErrorInvalidCurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
if (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Equals("en")) {
|
||||
return;
|
||||
// Skip translation progress for English and invariant (such as "C") cultures
|
||||
switch (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName) {
|
||||
case "en":
|
||||
case "iv":
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't dispose this resource set, as we can't be sure if it isn't used somewhere else, rely on GC in this case
|
||||
|
||||
@@ -352,7 +352,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
|
||||
private async Task<bool> MatchActivelyRound(IReadOnlyCollection<Steam.Asset.EType> acceptedMatchableTypes, IDictionary<ulong, (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs)> triedSteamIDs) {
|
||||
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0) || (triedSteamIDs == null)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(acceptedMatchableTypes) + " || " + nameof(triedSteamIDs));
|
||||
|
||||
@@ -8,6 +8,8 @@ RUN echo "node: $(node --version)" && \
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-dotnet
|
||||
ENV CONFIGURATION Release
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
ENV NET_CORE_VERSION netcoreapp3.1
|
||||
WORKDIR /app
|
||||
COPY --from=build-node /app/dist ASF-ui/dist
|
||||
@@ -21,14 +23,17 @@ RUN dotnet --info && \
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm --nologo && \
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm --nologo && \
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm --nologo && \
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=linux-arm -p:PublishTrimmed=true -r linux-arm --nologo && \
|
||||
cp "ArchiSteamFarm/overlay/linux-arm/ArchiSteamFarm-Service.sh" "out/ArchiSteamFarm-Service.sh"
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=linux-arm -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-arm --nologo && \
|
||||
if [ -d "ArchiSteamFarm/overlay/linux-arm" ]; then cp "ArchiSteamFarm/overlay/linux-arm/"* "out"; fi
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime-deps:3.1-buster-slim-arm32v7 AS runtime
|
||||
ENV ASPNETCORE_URLS=
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-dotnet /app/out .
|
||||
VOLUME ["/app/config", "/app/logs", "/app/plugins"]
|
||||
HEALTHCHECK CMD ["pidof", "-q", "ArchiSteamFarm"]
|
||||
ENTRYPOINT ["./ArchiSteamFarm-Service.sh", "--no-restart", "--process-required", "--system-required"]
|
||||
|
||||
@@ -8,6 +8,8 @@ RUN echo "node: $(node --version)" && \
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-dotnet
|
||||
ENV CONFIGURATION Release
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
ENV NET_CORE_VERSION netcoreapp3.1
|
||||
WORKDIR /app
|
||||
COPY --from=build-node /app/dist ASF-ui/dist
|
||||
@@ -21,14 +23,17 @@ RUN dotnet --info && \
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm64 --nologo && \
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm64 --nologo && \
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm64 --nologo && \
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=linux-arm64 -p:PublishTrimmed=true -r linux-arm64 --nologo && \
|
||||
cp "ArchiSteamFarm/overlay/linux-arm64/ArchiSteamFarm-Service.sh" "out/ArchiSteamFarm-Service.sh"
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=linux-arm64 -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-arm64 --nologo && \
|
||||
if [ -d "ArchiSteamFarm/overlay/linux-arm64" ]; then cp "ArchiSteamFarm/overlay/linux-arm64/"* "out"; fi
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime-deps:3.1-buster-slim-arm64v8 AS runtime
|
||||
ENV ASPNETCORE_URLS=
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-dotnet /app/out .
|
||||
VOLUME ["/app/config", "/app/logs", "/app/plugins"]
|
||||
HEALTHCHECK CMD ["pidof", "-q", "ArchiSteamFarm"]
|
||||
ENTRYPOINT ["./ArchiSteamFarm-Service.sh", "--no-restart", "--process-required", "--system-required"]
|
||||
|
||||
@@ -8,6 +8,8 @@ RUN echo "node: $(node --version)" && \
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-dotnet
|
||||
ENV CONFIGURATION Release
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
ENV NET_CORE_VERSION netcoreapp3.1
|
||||
WORKDIR /app
|
||||
COPY --from=build-node /app/dist ASF-ui/dist
|
||||
@@ -21,14 +23,17 @@ RUN dotnet --info && \
|
||||
dotnet build ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-x64 --nologo && \
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-x64 --nologo && \
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-x64 --nologo && \
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=linux-x64 -p:PublishTrimmed=true -r linux-x64 --nologo && \
|
||||
cp "ArchiSteamFarm/overlay/linux-x64/ArchiSteamFarm-Service.sh" "out/ArchiSteamFarm-Service.sh"
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=linux-x64 -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-x64 --nologo && \
|
||||
if [ -d "ArchiSteamFarm/overlay/linux-x64" ]; then cp "ArchiSteamFarm/overlay/linux-x64/"* "out"; fi
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime-deps:3.1-buster-slim AS runtime
|
||||
ENV ASPNETCORE_URLS=
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-dotnet /app/out .
|
||||
VOLUME ["/app/config", "/app/logs", "/app/plugins"]
|
||||
HEALTHCHECK CMD ["pidof", "-q", "ArchiSteamFarm"]
|
||||
ENTRYPOINT ["./ArchiSteamFarm-Service.sh", "--no-restart", "--process-required", "--system-required"]
|
||||
|
||||
@@ -8,6 +8,8 @@ RUN echo "node: $(node --version)" && \
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-dotnet
|
||||
ENV CONFIGURATION Release
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
ENV NET_CORE_VERSION netcoreapp3.1
|
||||
WORKDIR /app
|
||||
COPY --from=build-node /app/dist ASF-ui/dist
|
||||
@@ -22,13 +24,16 @@ RUN dotnet --info && \
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm --nologo && \
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm --nologo && \
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=docker -p:SelfContained=false -p:UseAppHost=false -r linux-arm --nologo && \
|
||||
cp "ArchiSteamFarm/overlay/generic/ArchiSteamFarm.sh" "out/ArchiSteamFarm.sh"
|
||||
if [ -d "ArchiSteamFarm/overlay/generic" ]; then cp "ArchiSteamFarm/overlay/generic/"* "out"; fi
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim-arm32v7 AS runtime
|
||||
ENV ASPNETCORE_URLS=
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-dotnet /app/out .
|
||||
VOLUME ["/app/config", "/app/logs", "/app/plugins"]
|
||||
HEALTHCHECK CMD ["pidof", "-q", "dotnet"]
|
||||
ENTRYPOINT ["./ArchiSteamFarm.sh", "--no-restart", "--process-required", "--system-required"]
|
||||
|
||||
@@ -8,6 +8,8 @@ RUN echo "node: $(node --version)" && \
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-dotnet
|
||||
ENV CONFIGURATION Release
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
ENV NET_CORE_VERSION netcoreapp3.1
|
||||
WORKDIR /app
|
||||
COPY --from=build-node /app/dist ASF-ui/dist
|
||||
@@ -22,13 +24,16 @@ RUN dotnet --info && \
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm64 --nologo && \
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-arm64 --nologo && \
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=docker -p:SelfContained=false -p:UseAppHost=false -r linux-arm64 --nologo && \
|
||||
cp "ArchiSteamFarm/overlay/generic/ArchiSteamFarm.sh" "out/ArchiSteamFarm.sh"
|
||||
if [ -d "ArchiSteamFarm/overlay/generic" ]; then cp "ArchiSteamFarm/overlay/generic/"* "out"; fi
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim-arm64v8 AS runtime
|
||||
ENV ASPNETCORE_URLS=
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-dotnet /app/out .
|
||||
VOLUME ["/app/config", "/app/logs", "/app/plugins"]
|
||||
HEALTHCHECK CMD ["pidof", "-q", "dotnet"]
|
||||
ENTRYPOINT ["./ArchiSteamFarm.sh", "--no-restart", "--process-required", "--system-required"]
|
||||
|
||||
@@ -8,6 +8,8 @@ RUN echo "node: $(node --version)" && \
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-dotnet
|
||||
ENV CONFIGURATION Release
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
ENV NET_CORE_VERSION netcoreapp3.1
|
||||
WORKDIR /app
|
||||
COPY --from=build-node /app/dist ASF-ui/dist
|
||||
@@ -22,13 +24,16 @@ RUN dotnet --info && \
|
||||
dotnet test ArchiSteamFarm.Tests -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-x64 --nologo && \
|
||||
dotnet clean ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -p:SelfContained=false -p:UseAppHost=false -r linux-x64 --nologo && \
|
||||
dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -f "$NET_CORE_VERSION" -o out -p:ASFVariant=docker -p:SelfContained=false -p:UseAppHost=false -r linux-x64 --nologo && \
|
||||
cp "ArchiSteamFarm/overlay/generic/ArchiSteamFarm.sh" "out/ArchiSteamFarm.sh"
|
||||
if [ -d "ArchiSteamFarm/overlay/generic" ]; then cp "ArchiSteamFarm/overlay/generic/"* "out"; fi
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS runtime
|
||||
ENV ASPNETCORE_URLS=
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT 1
|
||||
ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1
|
||||
LABEL maintainer="JustArchi <JustArchi@JustArchi.net>"
|
||||
EXPOSE 1242
|
||||
WORKDIR /app
|
||||
COPY --from=build-dotnet /app/out .
|
||||
VOLUME ["/app/config", "/app/logs", "/app/plugins"]
|
||||
HEALTHCHECK CMD ["pidof", "-q", "dotnet"]
|
||||
ENTRYPOINT ["./ArchiSteamFarm.sh", "--no-restart", "--process-required", "--system-required"]
|
||||
|
||||
40
appveyor.yml
40
appveyor.yml
@@ -98,7 +98,7 @@ build_script:
|
||||
}
|
||||
|
||||
|
||||
dotnet build ArchiSteamFarm -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" --nologo
|
||||
dotnet build ArchiSteamFarm -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" -p:UseAppHost=false --nologo
|
||||
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
@@ -106,7 +106,7 @@ build_script:
|
||||
}
|
||||
|
||||
|
||||
dotnet build ArchiSteamFarm.CustomPlugins.ExamplePlugin -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" --nologo
|
||||
dotnet build ArchiSteamFarm.CustomPlugins.ExamplePlugin -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" -p:UseAppHost=false --nologo
|
||||
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
@@ -121,7 +121,7 @@ test_script:
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
|
||||
dotnet test ArchiSteamFarm.Tests -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" --nologo
|
||||
dotnet test ArchiSteamFarm.Tests -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" -p:UseAppHost=false --nologo
|
||||
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
@@ -136,6 +136,22 @@ after_test:
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
|
||||
dotnet clean ArchiSteamFarm -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" -p:UseAppHost=false --nologo
|
||||
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
|
||||
dotnet restore ArchiSteamFarm
|
||||
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
|
||||
$PublishBlock = {
|
||||
param($variant)
|
||||
|
||||
@@ -154,7 +170,7 @@ after_test:
|
||||
if ($variant -like 'generic*') {
|
||||
$variantArgs = '-p:UseAppHost=false'
|
||||
} else {
|
||||
$variantArgs = '-p:PublishTrimmed=true', '-r', "$variant"
|
||||
$variantArgs = '-p:PublishSingleFile=true', '-p:PublishTrimmed=true', '-r', "$variant"
|
||||
}
|
||||
|
||||
dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -f "$targetFramework" -o "out\$variant" "-p:ASFVariant=$variant" --no-restore --nologo $variantArgs
|
||||
@@ -202,22 +218,6 @@ after_test:
|
||||
}
|
||||
|
||||
|
||||
dotnet clean ArchiSteamFarm -c "$env:CONFIGURATION" -f "$env:NET_CORE_VERSION" --nologo
|
||||
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
|
||||
dotnet restore ArchiSteamFarm
|
||||
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
|
||||
foreach ($variant in $env:VARIANTS.Split([char[]] $null, [System.StringSplitOptions]::RemoveEmptyEntries)) {
|
||||
Start-Job -Name "$variant" $PublishBlock -ArgumentList "$variant"
|
||||
}
|
||||
|
||||
2
wiki
2
wiki
Submodule wiki updated: 5c75fad9a6...9892a76ff0
Reference in New Issue
Block a user