Skip to content

Commit

Permalink
Add bestfish project
Browse files Browse the repository at this point in the history
  • Loading branch information
kungfux committed Jan 21, 2024
1 parent 252c58e commit a214dc8
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 0 deletions.
1 change: 1 addition & 0 deletions _drafts/exploring-the-world-of-electronics.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: Exploring the world of electronics
date: 2019-11-14 03:12:39 +0300
categories: [Projects]
tags: [C++, ESP8266]
img_path: assets/img/
---

## Story
226 changes: 226 additions & 0 deletions _posts/2015-05-16-aquaculture-management-and-analysis-software.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
---
title: Aquaculture management and analysis software
date: 2015-05-16 01:09:23 +0200
categories: [Projects]
tags: [DotNet, C#, WinForms, SQLite, InnoSetup, Delphi, Batch Scripting]
mermaid: true
img_path: assets/img/aquaculture-management-and-analysis-software/
---

## Story
I have done some small projects so far, as my colleague suggested me to develop a solution for the research institute in 2014. This was a great opportunity for me to build a more complex, larger and hopefully professional solution. We worked with the customer to gather requirements and understand their needs. Since it takes time to understand their business, I built a basic prototype to demonstrate the core features and get early feedback. I had to think about how to model their data, design the UI/UX for their specific tasks, and think about how to make it easy to install and use for people who are not technical and have basic computer skills.

In addition to trying to make the project itself more professional, I also wanted to make the project management more organized. I had to work with several people with different roles in the project, so I started using the project management tool Redmine to manage tasks, assign responsibilities, and track progress. Releases and previews were available for download from FTP server. For coding, I followed best practices such as using SVN for code versioning and Jenkins for continuous integration.

Due to time and budget constraints, it was decided to go with a single-user desktop application where the operator would work on the computer running this software. While it was possible to work with the same database from multiple workstations, I informed the client that it would be better to customize the solution to fully support multi-user access in the future if needed.

## Demo
{% include embed/youtube.html id='X8vJlxaz5YM' %}

## Requirements
- Create application to manage aquaculture farm data
- Add reporting for analysis
- Add document management to attach documentation and reports to records
- Create an installer
- Create accompanying documentation
- Create end-user help system

## Solution
Stack used:
- C#/.NET WinForms
- SQLite
- InnoSetup
- Delphi

The core application was built using C# and .NET WinForms as the UI framework for its rich controls and ease of development.

### UI/UX
The client's vision of their workflow and tasks was mapped to forms, views, and navigation between them. I decided to use the Multiple Document Interface (MDI) approach to allow multiple document windows within the parent application window. Child windows could be minimized, maximized, and closed. I added a menu to display the currently open windows to quickly switch between them and organize them cascading, vertically, or horizontally like in Windows.

The application consists of a main menu with forms accessible through it. Each form has a toolbar with common actions such as Create New, Edit, Save, Print, etc. Context menus provide actions relevant to selected items. There are many forms with grids to display data in tables. To make it easier to read data in tables and to customize columns based on user preferences, I had to implement custom controls. These include the `SmartDataGridViewTable`, `SmartListView`, and `SmartTableLayoutPanel` controls, which allow you to
- Apply alternating row colors to improve readability
- Hide/show columns
- Change column order, width
- Store user column preferences in the Windows registry and restore them on form load

Forms are designed to be simple and focus on single tasks. Complex tasks have been split into multiple forms to avoid clutter. Navigation was made easy through the toolbar; for example, there was no need to navigate back to the grid form to access the previous or next record in the list. Any referenced item was accessible from the current context through double-click navigation.

### Data storage
SQLite was used for data storage as a lightweight solution that was easy to back up and didn't require a database server. As I continued to work on the project after the initial release, I needed to implement database versioning to handle schema changes. This was done by storing the database version number in a table and checking it at application startup. If a newer version was required, an upgrade script would perform the necessary transformations using a transaction to keep the database up to date.

```sql
PRAGMA foreign_keys = ON;

-- history of database updates
CREATE TABLE [BF_UPDATES_HISTORY]
(
[id] INTEGER PRIMARY KEY AUTOINCREMENT,
[when] TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
[version] VARCHAR(15) NOT NULL
);

-- current version of database
CREATE VIEW [BF_VIEW_CURRENT_VERSION] AS
SELECT version
FROM BF_UPDATES_HISTORY
WHERE id = (SELECT MAX(id) FROM BF_UPDATES_HISTORY);

-- set default database's version
INSERT INTO BF_UPDATES_HISTORY (version) VALUES ('1.0.0.0');
```

I decided to store documents not in the database, but in the file system. A `Documents` table contains only metadata and a file path. The file is copied to the special folder during file import and a record is created in the database. This makes it easy to open and modify attached files while keeping the database lightweight. Files that should not be modified are copied and marked read-only in the file system, so that the user can still view them, but cannot modify them. For example, sample documents and formal templates.

To display the appropriate icon for the document in the list, I checked if the path to the icon file was present in the registry at the path `HKEY_CLASSES_ROOT\<extension>\DefaultIcon` and extracted the icon from that file. If not, I used the combination of `SHGetFileInfo` and `ExtractIconEx` WinAPI functions. SHGetFileInfo' returns the index of the system icon for a given file type based on its extension. ExtractIconEx' uses this index to extract the actual icon from the system icon resources.

![Icons for attached documents](document-icons.png)
_Icons for attached documents_

### Reporting
To generate reports from the data, I created a base abstract class `ReportSkeleton` that other report classes could inherit from. It contained common properties used by the print form to define whether the user is allowed to use filters, filter values, etc., and methods to retrieve data from the database based on those filters. Reports would inherit from this base class and override the methods to define their specific data source and fields to display.

Reports could be printed or exported to Word, Excel, and HTML documents. In fact, each report produces its output in HTML format and saves it with a different extension, depending on the export type. The trick is that both Word and Excel applications can open such files and display and format the data accordingly. Using the web format also makes it easy to display the print preview window.

### Distribution
To distribute the application, InnoSetup was used to create a Windows installer. It provides a wizard-style interface to guide the user through the installation. I built a typical installer file, except for some custom logic to check if the .NET dependency is installed on the machine or not. This was a necessary step because the minimum supporting Windows version was Windows XP SP2, where .NET was not a mandatory part of the system.

```pascal
[Code]
const
DotNetVersion='v4';
function InitializeSetup(): Boolean;
begin
Result := true;
if (not RegKeyExists(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\' + DotNetVersion)) then
begin
MsgBox(FmtMessage(CustomMessage('DotNetIsNotInstalled'), [DotNetVersion]), mbError, MB_OK);
Abort();
end;
end;
[CustomMessages]
DotNetIsNotInstalled='Програма Bestfish.NET не може бути встановлена, так як на даному комп''ютері не встановлений Microsoft .NET Framework %1. Вставте інсталяційний CD диск в оптичний привід вашого комп''ютера і встановіть всі необхідні доповнення. Після чого спробуйте знову'.
```

To automate the build process, I created batch scripts to build the application, create the database, and generate the installer. This allowed me to minimize the build step logic in Jenkins and keep it up-to-date directly in the codebase.

```batch
:: Configuration parameters
set SetupOutputFolder=X:\output
set sqlite3=X:\3rd_party\util\sqlite3.exe
set databasesql=X:\install\db.sql
set compiler=%WINDIR%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
set keys=/p:Configuration=Release
set projectfile=X:\project\Bestfish.NET.csproj
set setupfile=X:\install\bestfish.iss
:: Check is X: exists
if not exist X:\makefile.bat set XERROR=1
if [%XERROR%]==[1] echo ERROR! X: disk not found! Call makeX.bat to fix this.
if [%XERROR%]==[1] exit /B 1
:: Remember 1st argument from command line
set argument=%1
:: Check argument
if [%argument%]==[Build] goto Build
if [%argument%]==[Database] goto Database
if [%argument%]==[Setup] goto Clean
:Help
:: Display help
echo Script can be used with following arguments:
echo * Build - just compile program;
echo * Database - just create new database file;
echo * Setup - compile release + database + pack installation file.
echo .
goto :EOF
:Build
:: Perform debug build of project
echo *** Perform operation: Build project ***
:: Check is compiler exists
if not exist %compiler% set COMPERROR=1
if [%COMPERROR%]==[1] echo ERROR: Compiler not found!
if [%COMPERROR%]==[1] exit /B 1
:: Check is project file exists
if not exist %projectfile% set PROJECTFILEERROR=1
if [%PROJECTFILEERROR%]==[1] echo ERROR: Project file not found!
if [%PROJECTFILEERROR%]==[1] exit /B 1
:: Compile
%compiler% %keys% %projectfile%
if ERRORLEVEL==1 echo ERROR: Project was not built properly!
if ERRORLEVEL==1 exit /B 1
copy /V X:\project\bin\Release\*.* %SetupOutputFolder%
if ERRORLEVEL==1 echo ERROR: Coping to output folder was not done properly!
if ERRORLEVEL==1 exit /B 1
echo *** Job done ***
:: Go to next step or exit
if [%argument%]==[Setup] goto Database
goto :EOF
:Database
:: Perform creation of new database
:: Check is sqlite command line tool exists
if not exist %sqlite3% set SQLITEERROR=1
if [%SQLITEERROR%]==[1] echo ERROR: sqlite3.exe not found!
if [%SQLITEERROR%]==[1] exit /B 1
:: Check is .sql file exists. If OK then perform database making
if not exist %databasesql% set SQLFILEERROR=1
if [%SQLFILEERROR%]==[1] echo ERROR: database.sql file not found!
if [%SQLFILEERROR%]==[1] exit /B 1
:: Remove existing database
echo *** Perform operation: Removing existing database ***
del /Q %SetupOutputFolder%\db.sqlite
if ERRORLEVEL==1 echo ERROR: Existing database can not be deleted!
:: Perform operation
echo *** Perform operation: Make new database ***
%sqlite3% %SetupOutputFolder%\db.sqlite < %databasesql%
if ERRORLEVEL==1 echo ERROR: Database was not created properly!
if ERRORLEVEL==1 exit /B 1
echo *** Job done ***
:: Go to next step or exit
if [%argument%]==[Setup] goto Pack
goto :EOF
:Pack
:: Perform packing of installation file
echo *** Perform operation: Pack installation file ***
:: Check is Inno Setup 5 exists
if not exist "C:\Program Files\Inno Setup 5\ISCC.exe" set SETUPERROR=1
if [%SETUPERROR%]==[1] echo ERROR: Inno Setup 5 not found!
if [%SETUPERROR%]==[1] exit /B 1
:: Pack
"C:\Program Files\Inno Setup 5\ISCC.exe" %setupfile%
if ERRORLEVEL==1 echo ERROR: Installation file was not created properly!
if ERRORLEVEL==1 exit /B 1
echo *** Job done ***
goto :EOF
:Clean
:: Perform cleaning of output folder
echo *** Perform operation: Cleaning workspace ***
del /S /Q /A-H %SetupOutputFolder%
if ERRORLEVEL==1 echo ERROR: Workspace was not cleaned!
if ERRORLEVEL==1 goto End
echo *** Job done ***
if [%argument%]==[Setup] goto Build
goto :EOF
```

In addition to the installer, I was asked to create an ISO image that could be burned to a CD that would contain all the necessary files and have an autorun feature. However, there was no guarantee that .NET would be installed on the machine where the CD would be used. So I had to create an autorun application using Delphi. So when the disk is inserted, the system reads `autorun.inf` and launches a Delphi application that helps install the necessary dependencies, read the associated documentation, and install the software I developed.

![Autorun application](autorun.png)
_Autorun application_

It was interesting to figure out what system requirements should be defined for the end product. The good news is that there were requirements available from the frameworks being used to build the project that I used to define system requirements for my applications.

We created the help system using the Compiled HTML (CHM) format, which is a standard Windows format for creating help files. This allowed end users to easily access the help directly from within the application.

## Conclusion
- It's not enough to build an application that might solve a user need. There is a huge overhead in maintaining, supporting, and preparing applications for deployment. You need to think about these issues from the beginning to properly plan and estimate project timelines.
- Infrastructure and management software is just as important as the core application code. They help you plan, meet deliverables, and maintain the product over time.

## Extras
{% include embed/youtube.html id='21sK0JMU9aI' %}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a214dc8

Please sign in to comment.