Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 196 additions & 60 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,196 @@
# Contributing to Windows Memory Cleaner

Thank you for your interest in contributing! Contributions are what make the open-source community such a great place to learn, inspire, and create. Here’s how you can help.

---

## How Can I Contribute?

We use GitHub Issues to track all bugs, feature requests, and questions. Please use the appropriate template for your submission.

### 🐞 Reporting Bugs

If you've found a bug, please use the **[Bug Report](../../issues/new?template=bug_report.yml)** template. Be sure to include:
- Your application and Windows version.
- Steps to reproduce the issue.
- A clear description of the expected and actual behavior.

### 🚀 Suggesting Enhancements

Have an idea for a new feature or an improvement? Use the **[Feature Request](../../issues/new?template=feature_request.yml)** template and describe:
- The problem you are trying to solve.
- Your proposed solution.

### 🌐 Submitting Translations

To add a new translation or update an existing one, please use the **[Translation Request](../../issues/new?template=translation_request.yml)** template.

### ❓ Asking Questions

If you have a question about the project, please use the **[Question](../../issues/new?template=question.yml)** template. This helps us keep all support-related communication in one place.

---

## Pull Requests

1. **Fork** the repo and create your branch from `develop`.
2. If you’ve added code that should be tested, please add tests.
3. Ensure your code builds and passes all checks.
4. Fill out the PR template and link any related issues.

### Coding Standards

- Follow [Microsoft C# coding conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions).
- Write clear and concise commit messages.

### Code of Conduct

Please review our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you agree to uphold these standards.

### Security

If you discover a potential security vulnerability, please see our [Security Policy](SECURITY.md) for guidance on how to report it.

### License

By contributing, you agree that your contributions will be licensed under the [GPL-3.0 License](LICENSE).

---

Thank you for helping make it better!
# Contributing to WinMemoryCleaner

Thank you for your interest in contributing! This document outlines the process and standards for contributing to this project.

## Getting Started

1. **Fork** the repository on GitHub
2. **Clone** your fork locally
3. **Add upstream remote**: `git remote add upstream https://github.com/IgorMundstein/WinMemoryCleaner.git`
4. **Create a branch**: `git checkout -b feature/your-feature-name` or `fix/issue-number-description`

## Development Setup

### Prerequisites
- Windows 7 SP1 / Server 2012+
- [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
- Administrator privileges (required to run optimizations)

### Build & Run
```bash
# Restore dependencies
dotnet restore src/WinMemoryCleaner.csproj

# Build (Release)
dotnet build src/WinMemoryCleaner.csproj -c Release

# Run (framework-dependent)
dotnet run --project src/WinMemoryCleaner.csproj -c Release

# Or publish single-file
dotnet publish src/WinMemoryCleaner.csproj -c Release -o publish
```

## Code Style & Conventions

### C# Style
- **Language Version**: Latest (`LangVersion latest` in csproj)
- **Project Format**: SDK-style with `PackageReference`
- **Nullable**: Disabled (`<Nullable>disable</Nullable>`)
- **Implicit Usings**: Disabled
- **Formatting**: Follow existing code style (4-space indent, braces on new lines)

### Naming Conventions
- **Classes/Interfaces**: PascalCase (`ComputerService`, `IComputerService`)
- **Methods/Properties**: PascalCase (`Optimize`, `MemoryAreas`)
- **Fields**: `_camelCase` (`_memory`, `_cancellationTokenSource`)
- **Parameters/Locals**: camelCase (`processName`, `isOptimizing`)
- **Constants**: PascalCase (`AutoUpdateInterval`)

### Architecture Patterns
- **MVVM**: ViewModels in `ViewModel/`, Views in `View/`
- **Dependency Injection**: `DependencyInjection.Container.Register<TInterface, TImplementation>()`
- **Interfaces**: Prefix with `I` (`INotificationService`)
- **Async**: Prefer `async`/`await` over `.Result`/`.Wait()`

### Resource Management
- Implement `IDisposable` for classes holding unmanaged resources
- Use `Marshal.AllocHGlobal`/`FreeHGlobal` instead of `GCHandle.Alloc` for native interop
- Always dispose `IDisposable` in `finally` blocks or `using` statements

### Exception Handling
- **Never** use empty `catch { }` blocks
- Log exceptions with context: `Logger.Debug("Operation failed: " + ex.Message)`
- Use `ArgumentOutOfRangeException` for invalid enum values (not `NotImplementedException`)
- Implement `ConvertBack` for all `IValueConverter` implementations

## Pull Request Process

### Before Submitting
- [ ] Code builds clean: `dotnet build -c Release` (0 errors, 0 new warnings)
- [ ] Tested manually (both admin and non-admin scenarios)
- [ ] No breaking changes without discussion
- [ ] Updated `CHANGELOG.md` if user-facing changes
- [ ] Translations: **lowercase only** (app handles capitalization)

### PR Title Format
- `feat: Add new feature description` (new functionality)
- `fix: Resolve issue #XXX - brief description` (bug fixes)
- `refactor: Improve X without behavior change` (code improvements)
- `perf: Optimize X for better performance` (performance)
- `docs: Update documentation for X` (documentation)

### PR Description Template
```markdown
## Summary
Brief description of changes

## Related Issues
Fixes #XXX
Relates to #YYY

## Changes
- List of specific changes
- Another change

## Testing
- [ ] Build passes
- [ ] Tested as admin (optimizations work)
- [ ] Tested non-admin (graceful errors)
- [ ] Single-file publish works
- [ ] Localization loads correctly

## Screenshots (if UI changes)
```

## Translation Contributions

### Adding a New Language
1. Copy `src/Resources/Localization/English.json`
2. Rename to `{Locale-Description}.json` (e.g., `Slovenian.json`)
3. Translate **all values to lowercase** (app auto-capitalizes)
4. Save as UTF-8
5. Test: Place file next to `WinMemoryCleaner.exe` and launch
6. Submit PR or use [Translation Request template](https://github.com/IgorMundstein/WinMemoryCleaner/issues/new?template=translation_request.yml)

### Updating Existing Translations
- Only modify values, never keys
- Keep lowercase format
- Maintain placeholder formatting: `{0}`, `{1}`, etc.

## Issue Reporting

### Bug Reports
Use the [Bug Report template](https://github.com/IgorMundstein/WinMemoryCleaner/issues/new?template=bug_report.yml) with:
- OS version (`winver`)
- .NET version (`dotnet --version`)
- Steps to reproduce
- Expected vs actual behavior
- Event Viewer logs (source: "Windows Memory Cleaner")

### Feature Requests
Use the [Feature Request template](https://github.com/IgorMundstein/WinMemoryCleaner/issues/new?template=feature_request.yml) with:
- Use case description
- Proposed solution
- Alternatives considered

## Testing Guidelines

### Manual Testing Checklist
- [ ] App launches without errors (admin + non-admin)
- [ ] All 32+ languages load correctly
- [ ] Tray icon appears and updates
- [ ] Optimizations run and log to Event Viewer
- [ ] Settings persist to registry (HKLM)
- [ ] Service install/uninstall works
- [ ] Auto-update check doesn't crash
- [ ] Single-file publish runs (`PublishSingleFile=true`)
- [ ] Compact mode toggles
- [ ] Hotkey registration works

### Automated Tests
- Run: `dotnet test src/WinMemoryCleaner.csproj -c Release`
- Add tests for new functionality in `src/Test/`

## Architecture Overview

```
src/
├── App.xaml.cs # Application entry, lifecycle, single-instance
├── Core/
│ ├── Localizer.cs # Localization (lazy init, cached, fallback)
│ ├── Settings.cs # Registry persistence (thread-safe, ConcurrentDictionary)
│ ├── Logger.cs # Structured logging (EventLog, console, trace)
│ ├── Updater.cs # GitHub API, HttpClient, atomic update
│ ├── ThemeManager.cs # Theme loading, brush caching
│ └── ComputerService.cs # Native memory optimization (Marshal.AllocHGlobal)
├── Service/
│ ├── NotificationService.cs # Tray icon, memory usage rendering
│ ├── HotKeyService.cs # Global hotkeys (ConcurrentDictionary)
│ └── ComputerService.cs # IComputerService implementation
├── WindowsService/
│ ├── WinService.cs # Background service (Interlocked guard)
│ └── WinServiceInstaller.cs # sc.exe-based installer
├── ViewModel/
│ ├── MainViewModel.cs # Main UI logic, commands
│ └── Base/ViewModel.cs # Base VM with IsBusy, Navigation
├── Model/
│ ├── Localization.cs # 74 localized strings (public setters)
│ ├── Memory/*.cs # Memory stats structures
│ └── OperatingSystem.cs # OS version detection (fixed)
├── Interop/
│ ├── NativeMethods.cs # P/Invoke signatures (SupportedOSPlatform)
│ └── ShellInterop.cs # Shell links (IPersistFile)
└── Test/ # Unit/Integration tests
```

## Release Process (Maintainers Only)

1. Update version in `src/WinMemoryCleaner.csproj` and `src/Properties/AssemblyInfo.cs`
2. Update `CHANGELOG.md`
3. Create GitHub Release with `dotnet publish` artifacts
4. SignPath.io handles code signing automatically via CI/CD

## Questions?

Open a [Discussion](https://github.com/IgorMundstein/WinMemoryCleaner/discussions) or check existing [Issues](https://github.com/IgorMundstein/WinMemoryCleaner/issues).
71 changes: 35 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,62 +45,41 @@ jobs:
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
uses: actions/checkout@v4

- name: Cache NuGet packages
- name: Setup .NET
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
uses: actions/cache@v4
uses: actions/setup-dotnet@v4
with:
path: ~/.nuget/packages
key: nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
nuget-
dotnet-version: '8.0.x'
cache: true

- name: Setup MSBuild
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
uses: microsoft/setup-msbuild@v2

- name: Restore NuGet packages
- name: Restore dependencies
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
shell: pwsh
run: nuget restore src\WinMemoryCleaner.sln
run: dotnet restore src/WinMemoryCleaner.sln

- name: Build solution
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
shell: pwsh
run: msbuild src\WinMemoryCleaner.sln /m /p:Configuration=Release /p:Platform="Any CPU"
run: dotnet build src/WinMemoryCleaner.sln --configuration Release --no-restore

- name: Run Tests
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
shell: pwsh
run: |
$testAssembly = "src\bin\Release\WinMemoryCleaner.exe"
$testRunner = "src\packages\NUnit.Runners.2.6.4\tools\nunit-console.exe"

if (-Not (Test-Path $testRunner)) {
Write-Host "::error::Test runner not found at $testRunner"
exit 1
}

Write-Host "Running tests..."
& $testRunner $testAssembly /xml:TestResults.xml

if ($LASTEXITCODE -ne 0) {
Write-Host "::error::Tests failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
run: dotnet test src/WinMemoryCleaner.sln --configuration Release --no-build --logger "trx;LogFileName=TestResults.trx"

- name: Upload test results
if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true')
uses: actions/upload-artifact@v4
with:
name: test-results-${{ github.run_number }}
path: TestResults.xml
path: '**/TestResults.trx'
retention-days: 30

- name: Publish test results
if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true')
uses: EnricoMi/publish-unit-test-result-action/windows@v2
with:
files: TestResults.xml
files: '**/TestResults.trx'
check_name: Unit Test Results

- name: Get App Version
Expand Down Expand Up @@ -132,13 +111,33 @@ jobs:
exit 1
}

- name: Upload unsigned EXE as artifact
- name: Publish self-contained single-file
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
shell: pwsh
run: |
dotnet publish src/WinMemoryCleaner.csproj `
--configuration Release `
--runtime win-x64 `
--self-contained true `
/p:PublishSingleFile=true `
/p:IncludeAllContentForSelfExtract=true `
--output publish/self-contained

- name: Upload self-contained EXE as artifact
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
id: upload-unsigned
uses: actions/upload-artifact@v4
with:
name: winmemorycleaner-${{ steps.version.outputs.app_version }}
path: src\bin\Release\WinMemoryCleaner.exe
name: winmemorycleaner-${{ steps.version.outputs.app_version }}-selfcontained
path: publish/self-contained/WinMemoryCleaner.exe
if-no-files-found: error

- name: Upload framework-dependent build artifact
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || steps.check_files.outputs.any_changed == 'true'
uses: actions/upload-artifact@v4
with:
name: winmemorycleaner-${{ steps.version.outputs.app_version }}-framework-dependent
path: src/bin/Release/net8.0-windows/win-x64/publish/
if-no-files-found: error

- name: Submit to SignPath (Develop CI Signing)
Expand Down Expand Up @@ -170,7 +169,7 @@ jobs:
if ('${{ steps.signpath.conclusion }}' -eq 'success') {
echo "name=winmemorycleaner-${{ steps.version.outputs.app_version }}-signed" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
} else {
echo "name=winmemorycleaner-${{ steps.version.outputs.app_version }}" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
echo "name=winmemorycleaner-${{ steps.version.outputs.app_version }}-selfcontained" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
}

test-ui:
Expand Down Expand Up @@ -267,4 +266,4 @@ jobs:
os_name="${filename#screenshot-}"
os_name="${os_name%.png}"
echo "| $os_name | [$filename]($ARTIFACT_URL) |" >> $GITHUB_STEP_SUMMARY
done
done
Loading
Loading