顯示具有 Programming Lang. 標籤的文章。 顯示所有文章
顯示具有 Programming Lang. 標籤的文章。 顯示所有文章

2021年12月15日 星期三

Building a web site by Codeigniter 4 (CI4)

    I recently tried to build up a website by Codeigniter4 (known as CI4) and thought it is a pretty good framework.
    There are many tutorials and docs on the internet, so I skipped those things in this article. I'll probably focus on the issues I met during website development. 

Tools list
  • XDebug (Local debug)
  • VS Code with plugin 'PHP Debug' (Editor)
  • XAMPP (Local server)
  • Composer (Codeigniter4 and other comonents)
  • Bootstrap 5 (UI components)
Issues: 
  1. Public tool functions
    I write some tool in the folder 'Helpers' in order to use them accross all the controllers. I'm not sure it is the right way to do that.
  2. Modals in Bootstrap 5
    This is very usefull UI and easy to use except for one condition which is switching between modals. In my case, I need to switch modals according to what stage it is now. Unlikely, the POST data will be cleaned after modal switching. For now, I don't have a solution.
    Instead, I use POST method in very modal and check what stage is now and show the related modal by using javascript.
  3. Authentication check
    I write some authenticaiton check in the folder 'Filters' in order to check if the user has the permission to use the routing (page). In general, it is very convient. But, in some cases, the logic would be complicated if the page has multiple modes. Perhaps, it is due to two type users in my case. One is authenticated by other web site and the other is authenticated by my own web site.
  4. Complex Routing
    I'm still working on organizing the routing strategy. I have amost 50 routing pathes in my route definitions and it is not friendly readable. It is a very minor issue but need to fix it in the future.

2019年5月30日 星期四

[C#] Enable index in SQLite (Code First)

We should specify which column would be indexed in the function 'OnModelCreating'


        protected override void OnModelCreating( ModelBuilder modelBuilder )
        {
            modelBuilder.Entity().HasIndex(b => new { b.Time });
            modelBuilder.Entity().HasIndex(b => new { b.CarAppraisalLicense });
            modelBuilder.Entity().HasIndex(b => new { b._CreateTime});
            modelBuilder.Entity().HasIndex(b => new { b._ModifiedTime });

            modelBuilder.Entity().HasIndex(b => new { b.carPlateModify});
            modelBuilder.Entity().HasIndex(b => new { b.licenseRfid});
            modelBuilder.Entity().HasIndex(b => new { b.carRfid});
            modelBuilder.Entity().HasIndex(b => new { b.updateTime });

            modelBuilder.Entity().HasIndex(b => new { b.licenseRfid });
            modelBuilder.Entity().HasIndex(b => new { b.licenseNo});
            modelBuilder.Entity().HasIndex(b => new { b.updateTime});
        }

Then do EF migration, downgrade/upgrade codes will be generated autometically.

    public partial class CreateIndex : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateIndex(
                name: "IX_VerifyLogs_CarAppraisalLicense",
                table: "VerifyLogs",
                column: "CarAppraisalLicense");

            migrationBuilder.CreateIndex(
                name: "IX_VerifyLogs_Time",
                table: "VerifyLogs",
                column: "Time");

            migrationBuilder.CreateIndex(
                name: "IX_VerifyLogs__CreateTime",
                table: "VerifyLogs",
                column: "_CreateTime");

            migrationBuilder.CreateIndex(
                name: "IX_VerifyLogs__ModifiedTime",
                table: "VerifyLogs",
                column: "_ModifiedTime");

            migrationBuilder.CreateIndex(
                name: "IX_DriverLicenses_licenseNo",
                table: "DriverLicenses",
                column: "licenseNo");

            migrationBuilder.CreateIndex(
                name: "IX_DriverLicenses_licenseRfid",
                table: "DriverLicenses",
                column: "licenseRfid");

            migrationBuilder.CreateIndex(
                name: "IX_DriverLicenses_updateTime",
                table: "DriverLicenses",
                column: "updateTime");

            migrationBuilder.CreateIndex(
                name: "IX_CarLicenses_carPlateModify",
                table: "CarLicenses",
                column: "carPlateModify");

            migrationBuilder.CreateIndex(
                name: "IX_CarLicenses_carRfid",
                table: "CarLicenses",
                column: "carRfid");

            migrationBuilder.CreateIndex(
                name: "IX_CarLicenses_licenseRfid",
                table: "CarLicenses",
                column: "licenseRfid");

            migrationBuilder.CreateIndex(
                name: "IX_CarLicenses_updateTime",
                table: "CarLicenses",
                column: "updateTime");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropIndex(
                name: "IX_VerifyLogs_CarAppraisalLicense",
                table: "VerifyLogs");

            migrationBuilder.DropIndex(
                name: "IX_VerifyLogs_Time",
                table: "VerifyLogs");

            migrationBuilder.DropIndex(
                name: "IX_VerifyLogs__CreateTime",
                table: "VerifyLogs");

            migrationBuilder.DropIndex(
                name: "IX_VerifyLogs__ModifiedTime",
                table: "VerifyLogs");

            migrationBuilder.DropIndex(
                name: "IX_DriverLicenses_licenseNo",
                table: "DriverLicenses");

            migrationBuilder.DropIndex(
                name: "IX_DriverLicenses_licenseRfid",
                table: "DriverLicenses");

            migrationBuilder.DropIndex(
                name: "IX_DriverLicenses_updateTime",
                table: "DriverLicenses");

            migrationBuilder.DropIndex(
                name: "IX_CarLicenses_carPlateModify",
                table: "CarLicenses");

            migrationBuilder.DropIndex(
                name: "IX_CarLicenses_carRfid",
                table: "CarLicenses");

            migrationBuilder.DropIndex(
                name: "IX_CarLicenses_licenseRfid",
                table: "CarLicenses");

            migrationBuilder.DropIndex(
                name: "IX_CarLicenses_updateTime",
                table: "CarLicenses");
        }
    }
After creating the index, we can reduce 1/10 DB query time on average.

[C#] Force app run as administrator

In order to force an app to run as administrator, we should add an Application Manifest file 'app.manifest' in the main project.

Below code is the default content of file 'app.manifest'.


<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <!-- UAC Manifest Options
             If you want to change the Windows User Account Control level replace the 
             requestedExecutionLevel node with one of the following.

        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />

            Specifying requestedExecutionLevel element will disable file and registry virtualization. 
            Remove this element if your application requires this virtualization for backwards
            compatibility.
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
        -->
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
      <applicationRequestMinimum>
        <defaultAssemblyRequest permissionSetReference="Custom" />
        <PermissionSet class="System.Security.PermissionSet" version="1" ID="Custom" SameSite="site" Unrestricted="true" />
      </applicationRequestMinimum>
    </security>
  </trustInfo>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- A list of the Windows versions that this application has been tested on
           and is designed to work with. Uncomment the appropriate elements
           and Windows will automatically select the most compatible environment. -->
      <!-- Windows Vista -->
      <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
      <!-- Windows 7 -->
      <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
      <!-- Windows 8 -->
      <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
      <!-- Windows 8.1 -->
      <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
      <!-- Windows 10 -->
      <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
    </application>
  </compatibility>
  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
  <!--
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>
  -->
  <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
  <!--
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
          type="win32"
          name="Microsoft.Windows.Common-Controls"
          version="6.0.0.0"
          processorArchitecture="*"
          publicKeyToken="6595b64144ccf1df"
          language="*"
        />
    </dependentAssembly>
  </dependency>
  -->
</assembly>

Replace "asInvoker" with "requireAdministrator" as following


        <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

After doing the above steps, you will be asked to restart Visual Studio under different credentials.

2019年5月22日 星期三

[Linux Shell Script] A script for parsing file, writing logs, archiving logs and taking args

We need a shell script to execute HTTP request on demand. This script shall read/parse text file and write/archive logs.
  • 3 sub functions
    1. get_script_dir : Get current folder path of this script
    2. echos : Write message to STDOut and log file
    3. archive_logs : Archive old logs to .tar
#!/bin/bash

# Functions
get_script_dir () 
{
 SOURCE="${BASH_SOURCE[0]}"
# While $SOURCE is a symlink, resolve it
 while [ -h "$SOURCE" ]; do
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$( readlink "$SOURCE" )"
# If $SOURCE was a relative symlink (so no "/" as prefix, need to resolve it relative to the symlink base directory
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
 done
 DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
 echo "$DIR/"
}

# DateFormat in log file : "[YYYY/MM/DD H:M:S]"
logDate=`date "+%Y%m%d"`
logFolder="$(get_script_dir)Logs/"

if [ ! -d $logFolder ]; then
 mkdir $logFolder
fi
logFile="${logFolder}CGI_Controller_${logDate}.log"

# Append STD out to file additionaly
echos ()
{
 logTime=`date "+%H:%M:%S"`
 builtin echo "[$logTime] $@" | tee -a "$logFile"
}

archive_logs()
{
 currentDate=`date "+%Y%m%d"`
 array=( $( ls ${logFolder} ) )
# Explicitly report array content.
 let i=0
 while (( ${#array[@]} > i )); do
 # echos "${i} ${array[i++]}"
 if [[ ${array[i]} == CGI_Controller_$currentDate* ]] || [[ ${array[i]} == zips ]]; then
  echos "The log is in the Current month, skipped it!"
 else
# Get target year and month for sufix of archived file name
  targetDate=`cut -d"_" -f 3 <<< "${array[i]}"`;
  targetDate=`cut -d"." -f 1 <<< $targetDate`;
  targetMonth="${targetDate:0:6}";
  zipFolder="$(get_script_dir)Logs/zips/"
  archivedFile="${zipFolder}Archived_${targetMonth}.tar.gz"
  if [ ! -d $zipFolder ]; then
   mkdir $zipFolder
  fi
  if [ -f "$archivedFile" ];then
   tar -rf $archivedFile --directory="${logFolder}" "${array[i]}" 
  else
   tar -cf $archivedFile --directory="${logFolder}" "${array[i]}" 
  fi
  echos "Archive file ${array[i]} to ${archivedFile}";
  rm "${logFolder}${array[i]}"
 fi
 ((i++))
 done
}
  • Main function
    1. Check if necessary files exist
    2. Check if parameter is valid
    3. Load setting file into an array
    4. Parsing settings and assemble CGI link
    5. Check server IP is UP
    6. Execute commands
# Main
# Check parameter number
if [ $# -ne 0 ]; then
 cmdI=$1
else
 echos "Parameter not found";
 exit 3
fi
# CMD array for each NVR
declare -a cmdArray

# Check if setting file existed
file="$(get_script_dir)config.txt"
if [ -f "$file" ];then
 echos "$file found";
else
 echos "$file not found, please create one";
 exit 4
fi

# Load file into array.
let i=0
while IFS=$'\n' read -r line_data; do
 cmdArray[i]="${line_data}"
((++i))
done < $file

# Roll back to item number
((--i))

if [ $i -le 0 ];then
 echos "No settings found in $file, please add CGI settings in this file. ([IP],[Path for Open],[Path for Close])";
 exit 5
fi

# Explicitly report array content.
#let i=0
#while (( ${#cmdArray[@]} > i )); do
# printf "${i} ${cmdArray[i++]}\n"
#done

# Make sure command number is less than settings number
if [ $cmdI -lt $i ]; then
 ip="$(cut -d';' -f1 <<<"${cmdArray[cmdI]}")"
 openPath="$(cut -d';' -f2 <<<"${cmdArray[cmdI]}")"
 closePath="$(cut -d';' -f3 <<<"${cmdArray[cmdI]}")"

 printf "${cmdI} ${cmdArray[cmdI]}\n"

 # Ping IP before executing it
 ping -c 1 -W 1 ${ip} &> /dev/null && result=0 || result=1

 if [ "${result}" == 0 ]; then
  echos "Server ${ip} is UP.";
 else
  echos "Server ${ip} is DOWN.";
  exit 6
 fi

 res="${logFolder}res"
 echos "http://${ip}${openPath}";
 wget -O $res "http://${ip}${openPath}";
 echos `cat $res`;
 # Delay 1 sec and then execute 2nd CGI command
 sleep 1
 echos "http://${ip}${closePath}";
 wget -O $res "http://${ip}${closePath}";
 echos `cat $res`;
 rm $res;
else
 echos "Parameter value is bigger than settings";
 exit 7
fi
archive_logs

2019年5月17日 星期五

[C#] HTTP host service in windows desktop app

We need to create HTTP host service in a windows desktop app to receive HTTP POST request. (Only one request will be handled at the same time.)

Use HttpListener and add necessary prefixes and start it


HttpListener server = new HttpListener();
string url = $"http://localhost:{port}/";
server.Prefixes.Add(url);

url = $"http://127.0.0.1:{port}/";
server.Prefixes.Add(url);

IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
url = $"http://{ipHostInfo.AddressList[0].ToString()}:{port}/";
server.Prefixes.Add(url);

server.Start();
logger.Info($"Listening to [{url}]...");
Use loop process each HTTP request

while( true )
{
  if( isStopping )
     break;
  HttpListenerContext context = server.GetContext();
  HttpListenerResponse response = context.Response;
  ...
}
Retrieve API path to determine which command is called

StringBuilder builder;
switch( context.Request.Url.LocalPath )
{
    case "/api/command1":
        builder = new StringBuilder("OK");
        break;
    default:
        builder = new StringBuilder("Unrecognized API");
        failureFlag = true;
        break;
}
Retrieve Body and convert it into JSON Object

try
{
    using( var reader = new StreamReader(context.Request.InputStream,
                                         context.Request.ContentEncoding) )
    {
        bodyText = reader.ReadToEnd();
        logger.Info($"HTTP POST body\n{bodyText}");

        var boardMsg = jsonSerializationUtil.DeserializeFromString(bodyText);
        if( boardMsg == null )
            builder = new StringBuilder("JSON format deserialization failure.");
        else
        {
           // Process boardMsg...
        }
    }
}
catch( Exception ex )
{
    logger.Warn(ex);
    builder = new StringBuilder("Body loading failure.");
}
Convert response string into bytes and send it back

string something = builder.ToString();
byte[] buffer = Encoding.UTF8.GetBytes(something);
response.ContentLength64 = buffer.Length;
System.IO.Stream st = response.OutputStream;
st.Write(buffer, 0, buffer.Length);

context.Response.Close();

[C#] DB data model for MySQL (Code First from database)

Create DB data model from MySQL (code first)

  1. Install NuGet packages 'MySql.Data' v.8.0.16 and 'MySql.Data.Entity' v. 6.10.8
  2. Install MySQL connector Net 6.10.8
  3. INstall MySQL for Visual Studio 1.2.8
  4. Add Entity Data Model (Code First from database)
  5. New Connection (MySQL Data Provider)
  6. Modify the file 'App.config' for DB connection string

<connectionStrings>
<add name="MySqlModel" connectionString="server=redmine2.gorilla-technology.com;user id=root;password=linuxyes;persistsecurityinfo=True;database=redmine" providerName="MySql.Data.MySqlClient" />
<add name="Default" connectionString="host=192.168.60.162;port=5432;database=redminedb;user id=redmine;password=MYSQL-TEL#53519427" providerName="Npgsql" />
</connectionStrings>


All needed works are done and then DB operations are ready to go.

using( var context = new MySqlModel() )
using( var transaction = context.Database.BeginTransaction() )
{
...
}

[Powershell] Auto packaging files by Powershell scripts

This is a script for packaging files and calculating MD5Sum as following steps.

  1. Get 3 parameters working folder, zip file name, and target file name
  2. Check if archive tool(7zip/WinRAR) existed in the working folder
  3. Get 7zip command from my FTP server
  4. Check if file/folder existed
  5. Archive target file into a zip file
  6. Calculate MD5Sum of the zip file


#Installer package

$DebugPreference = "Continue" # Default is SilentlyContinue

if($args.Count -ne 3){
  Write-Error "args.count=$($args.Count). It should be 3"
  return
}

$zip7FilePath="$PSScriptRoot\7zip\7za.exe"
$winrarFilePath="C:\Program Files\WinRAR\WinRAR.exe"
$solutionDir=$args[0] -replace "`n","" 
$archivedFilePath=$args[1] -replace "`n",""
$installerFilePath=$args[2] -replace "`n",""

# Return true if all required data exist
function checkRequiredData ($solutionDir) {
  Write-Debug "winrarFilePath=`"$winrarFilePath`""
  Write-Debug "7zipFilePath=`"$zip7FilePath`""
  if (!(Test-Path $winrarFilePath)){
    # WinRAR does not exist
    Write-Debug "`"$winrarFilePath`" doesn't exist, try to detect 7zip"
    if (!(Test-Path $zip7FilePath)){
      # 7za.exe does not exist
      Write-Debug "`"$zip7FilePath`" doesn't exist, try to extract it from zip"
      $url = "ftp://hanping-lab-pc/7zip.zip"
      $output = "$PSScriptRoot\7zip.zip"
      $start_time = Get-Date

      if( !(Test-Path $output)){
        # WinRAR does not exist
        Write-Debug "`"$output`" doesn't exist, try to download it from hanping-lab-pc ftp"
        Invoke-WebRequest -Uri $url -OutFile $output
        Write-Debug "Time taken: $((Get-Date).Subtract($start_time).Seconds) second(s)"
      }
      Expand-Archive -Path "$PSScriptRoot\7zip.zip" -DestinationPath "$PSScriptRoot"
      
      if (!(Test-Path $zip7FilePath)){
        Write-Error "`"$zip7FilePath`" doesn't exist"
        return $false
      }
    }
  }

  Write-Debug "solutionDir=`"$solutionDir`""
  # Check solution dir
  if(!(Test-Path $solutionDir)){
    Write-Error "solutionDir `"$solutionDir`" doesn't exist"
    return $false
  }

  # Check Installer
  Write-Debug "installerFilePath=`"$global:installerFilePath`""
  if(!(Test-Path $global:installerFilePath)){
    Write-Error "installerFilePath `"$global:installerFilePath`" doesn't exist"
    return $false
  }
  return $true
}

function getMD5Hash($fileName) {
  if([System.IO.File]::Exists($fileName)){
    $fileStream = New-Object System.IO.FileStream($fileName,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::ReadWrite)
    $md5Hash = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
    [byte[]]$fileByteChecksum = $md5Hash.ComputeHash($fileStream)
    $fileChecksum = ([System.Bitconverter]::ToString($fileByteChecksum)).Replace("-","")
    $fileStream.Close()
  } else {
    $fileChecksum = "ERROR: $fileName Not Found"
  }
  return $fileChecksum
}

function archive () {
  # Compress by WinRAR
  $version = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($global:installerFilePath).FileVersion
  $archivedFilePath = [System.IO.Path]::GetDirectoryName($archivedFilePath) + "\\" + [System.IO.Path]::GetFileNameWithoutExtension($archivedFilePath) + "_$($version).zip"
    # Remove existing archivedFile
  Write-Debug "archivedFilePath=`"$archivedFilePath`""
  if(Test-Path $archivedFilePath){
    Remove-Item $archivedFilePath -Recurse
  }
    if ((Test-Path $winrarFilePath)){
    # WinRAR does exist
      $process = [System.Diagnostics.Process]::Start("$winrarFilePath", "a -m5 -afzip -ep1` `"$archivedFilePath`" `"$global:installerFilePath`"") 
      $process.WaitForExit()
    }
    elseif ((Test-Path $zip7FilePath)){
    # 7-zip does exist
      $process = [System.Diagnostics.Process]::Start("$zip7FilePath", "a -tzip `"$archivedFilePath`" `"$global:installerFilePath`"") 
      $process.WaitForExit()
    }

  # Save MD5 of archivedFilePath as text file
  $md5Hash = (getMD5Hash($archivedFilePath)).toLower()
  Write-Debug "MD5=`"$md5Hash`""
  $dirName = [System.IO.Path]::GetDirectoryName($archivedFilePath)
  $fileNameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($archivedFilePath)
  $md5FilePath = Join-Path -Path $dirName -ChildPath ("$($fileNameWithoutExt)_MD5.txt")
  Write-Debug "md5FilePath=`"$md5FilePath`""
  $md5Hash | Set-Content $md5FilePath
}

#main region

Write-Debug "solutionDir=`"$solutionDir`""

if(checkRequiredData($solutionDir) == $true){
  archive
}

2019年5月16日 星期四

[.NET] How to build .NET NuGet package

Steps to build NuGet package for 'RFID.dll'
1. nuget.exe spec RFID.dll (nuget.exe download page)
2. Edit file [RFID.dll.nuspec]
3. Create Folder [lib\Uart\Bridge\RFIDLibNet\\] and put RFID.dll in it
    Note: The folder structure is like the value of tag 'id' in RFID.dll.nuspec.
4. nuget.exe pack RFID.dll.nuspec and then Uart.Bridge.RFIDLibNet.15.6.3.2.nupkg is created

The following folder tree is my build environment

│  CreateNuGetPackages.bat
│  README.md
│
├─outputs
│      Gorilla.Uart.Bridge.RFIDLibNet.15.6.3.2.nupkg
│
├─package
│  │  Package.nuspec
│  │
│  └─lib
│      └─Gorilla
│          └─Uart
│              └─Bridge
│                  └─RFIDLibNet
│                          RFID.dll

I write a script for auto-executing some commands

@echo off
set NUGET_PATH="C:\nugetPool\nuget.exe"
nuget.exe pack "package\Package.nuspec"
echo ============================================================
echo  Create NuGet Packages
echo ============================================================
"%NUGET_PATH%" pack "package\Package.nuspec"
pause
P.S.
 Files in the folder 'lib' will be referenced and files in the folder 'build' will be copied to the output (build) folder (ex. /bin/). Furthermore, you can customize some operations for different target platform in file 'xxx.xxx.xxx.targets'.


<!--Start Code-->
<?xml version="1.0"?>
<package >
<metadata>
<id>XXXX.Uart.Bridge.RFIDLibNet</id>
<version>15.06.03.2</version>
<authors>XXXX Technology Group</authors>
<owners>XXXX Technology Group</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Silicon Uart Bridge RFID Reader lib</description>
<releaseNotes>Official DLL version</releaseNotes>
<copyright>Copyright 2017</copyright>
<tags>XXXX Silicon UartBridge RFID</tags>
</metadata>
</package>
<!--End Code-->


2016年6月15日 星期三

[C#] Upload/Update/Check/Delete/List Video with YouTube API

找了很多關於C#上使用YouTube API的資料後,拼拼湊湊出簡易的YouTube Video處理的程式

以下不說明如何匯入YouTube API Lib與其使用申請,網路上很多,可自行參考。

基本的建立UserCredential,並宣告YouTubeService,每個動作都是需要此步驟

UserCredential credential;
using( var stream = new FileStream(System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "client_secrets.json"), FileMode.Open, FileAccess.Read) )
{
    credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
        GoogleClientSecrets.Load(stream).Secrets,
        new[] { YouTubeService.Scope.Youtube, YouTubeService.Scope.YoutubeUpload },
        "user",
        CancellationToken.None,
        new FileDataStore(System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "YoutubeCredential"))
    );
}
youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
    HttpClientInitializer = credential,
    ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
     

Upload
上傳單一影片檔案
var video = new Video();
video.Snippet = new VideoSnippet();
video.Snippet.Title = videoName;
video.Snippet.Description = videoName;
video.Snippet.Tags = new string[] { "LowRes", processingTag };
video.Snippet.CategoryId = conf.configMapper.ey_sec.ey_youtube_category;//"22"; See https://developers.google.com/youtube/v3/docs/videoCategories/list
video.Status = new VideoStatus();
video.Status.PrivacyStatus = conf.configMapper.ey_sec.ey_youtube_privacy; //"unlisted" or "private" or "public"
var filePath = target; // Replace with path to actual movie file.
try
{
    using( var fileStream = new FileStream(filePath, FileMode.Open) )
    {
        var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
        const int KB = 0x400;
        var minimumChunkSize = 256 * KB;
        videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
        videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;
        videosInsertRequest.ChunkSize = minimumChunkSize * 32;
        await videosInsertRequest.UploadAsync();      
    }
}
catch(Exception ex)
{
    logtext.Out("[YouTube Exception] " + ex.Message + ex.StackTrace);
}

Check&Update
檢查VideoID是否有效並檢查是否為Duplicate video,並更新Tag資訊
var my_video_request = youtubeService.Videos.List("snippet, status");
my_video_request.Id = oldVideoID; // the Youtube video id of the video you want to update
my_video_request.MaxResults = 1;
var my_video_response = await my_video_request.ExecuteAsync();
if( my_video_response.Items.Count == 0 )
{
    return;
}
var video = my_video_response.Items[0];
video.Snippet.Tags.Add(oldVersionTag);
if( video.Status.UploadStatus == "rejected" )
{
    logtext.Out("[YouTube Info] Video upload status : " + video.Status.UploadStatus);
    logtext.Out("[YouTube Info] Video upload reject reason : " + video.Status.RejectionReason);
}
// and tell the changes we want to youtube
var my_update_request = youtubeService.Videos.Update(video, "snippet, status");
my_update_request.Execute();


Delete
刪除特定VideoID之影片
try
{
    var my_video_request = youtubeService.Videos.List("snippet, status");
    my_video_request.Id = oldVideoID; // the Youtube video id of the video you want to update
    my_video_request.MaxResults = 1;
    var my_video_response = await my_video_request.ExecuteAsync();
    if( my_video_response.Items.Count != 0 )
    {
        logtext.Out("[Info ] Old VideoID [" + oldVideoID + "] is valid, then delete it.");
        var my_update_request = youtubeService.Videos.Delete(oldVideoID);
        my_update_request.Execute();
    }
}
catch(Exception ex)
{
    logtext.Out("[YouTube Exception] " + ex.Message + ex.StackTrace);
}

List
列出所有YouTube上的所有Video ID
try
{
    var channelsListRequest = youtubeService.Channels.List("contentDetails");
    channelsListRequest.Mine = true;
    var channelsListResponse = channelsListRequest.Execute();
    foreach( var channel in channelsListResponse.Items )
    {
        var uploadsListId = channel.ContentDetails.RelatedPlaylists.Uploads;
        var nextPageToken = "";
        while( nextPageToken != null )
        {
            var playlistItemsListRequest = youtubeService.PlaylistItems.List("snippet");
            playlistItemsListRequest.PlaylistId = uploadsListId;
            playlistItemsListRequest.MaxResults = 50;
            playlistItemsListRequest.PageToken = nextPageToken;
            var playlistItemsListResponse = playlistItemsListRequest.Execute();
            foreach( var playlistItem in playlistItemsListResponse.Items )
            {
                string id = playlistItem.Snippet.ResourceId.VideoId;
                videoIDList.Add(id);
            }
            nextPageToken = playlistItemsListResponse.NextPageToken;
        }
    }
}
catch( Exception ex )
{
    logtext.Out("[Exception] " + ex.Message + ex.StackTrace);
}

[C#] File access issue

為了確保檔案已無任何程式存取,個人使用了下列方式

檢查檔案的A C M time,確保其時間與當下時間差大於指定的秒數

public static bool CheckFileInUse(Logs logtext, string filePath, int timeout)
{
    if( !EFCS.FileExist(logtext, filePath, Conf.RUNNING_NO_CMD_MESSAGE) )
        return false;
    DateTime lastAT = System.IO.File.GetLastAccessTime(filePath);
    DateTime createT = System.IO.File.GetCreationTime(filePath);
    DateTime lastWT = System.IO.File.GetLastWriteTime(filePath);
    DateTime nowT = DateTime.Now;
    if( (nowT - lastAT).TotalSeconds < 0 ||
        (nowT - createT).TotalSeconds < 0 ||
        (nowT - lastWT).TotalSeconds < 0 )
    {
        System.IO.File.SetCreationTime(filePath, DateTime.Now);
        System.IO.File.SetLastAccessTime(filePath, DateTime.Now);
        System.IO.File.SetLastWriteTime(filePath, DateTime.Now);
    }
    lastAT = System.IO.File.GetLastAccessTime(filePath);
    createT = System.IO.File.GetCreationTime(filePath);
    lastWT = System.IO.File.GetLastWriteTime(filePath);
    nowT = DateTime.Now;
    if( (nowT - lastAT).TotalSeconds < timeout ||
        (nowT - createT).TotalSeconds < timeout ||
        (nowT - lastWT).TotalSeconds < timeout )
    {
        logtext.Out("[Info ] [" + filePath + "] creation/modification time interval < " + timeout + " secs");
        return true;
    }
    return false;
}
此外,另增加了File Lock檢查

public static bool CheckLock(Logs logtext, string _strSourceFileName)
{
    int i = 0;
    bool bResult = true;
    while( i < 20 )//Retry 20次
    {
        try
        {
            using( Stream stream = System.IO.File.Open(_strSourceFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite) )
            {
                if( stream != null )
                {
                    logtext.Out("[Info ] [" + _strSourceFileName + "] is ready.");
                    bResult = false;
                    break;
                }
            }
        }
        catch( FileNotFoundException ex )
        {
            logtext.Out("[Info ] [" + _strSourceFileName + "] is not ready. (" + ex.Message + ")");
            bResult = true;
        }
        catch( IOException ex )
        {
            logtext.Out("[Info ] [" + _strSourceFileName + "] is not ready. (" + ex.Message + ")");
            bResult = true;
        }
        catch( UnauthorizedAccessException ex )
        {
            logtext.Out("[Info ] [" + _strSourceFileName + "] is not ready. (" + ex.Message + ")");
            bResult = true;
        }
        finally
        {
            i++;
            System.Threading.Thread.Sleep(500);
        }
    }
    return bResult;
}

2015年5月25日 星期一

[C] File mappings and domain of NHibernate for SQLite DB

C#中針對SQLite使用NHibernate來存取DB資料

1. 在C#專案裡需安裝的Packages

NHibernate
Iesi.Collections

2. 額外使用到的軟體 (產出cs與xml檔案)
NHibernateMappingGenerator

設定參考

Entity不修改
Namespace (Domain)、Namespace (Map)、Assembly Name都設為專案名
 Connection選擇SQLite,並修改Data Source的路徑指向DB file

最後按下Generate All即可產出cs與xml檔案


3. 在C#專案中建立Domain與Mapping資料夾,分別放入cs檔案與xml檔案

4. 在C#專案中建立hibernate.cfg.xml
 
    NHibernate.Driver.SQLite20Driver
    Data Source=[DB檔案路徑];Version=3
    NHibernate.Dialect.SQLiteDialect
    true=1;false=0
    true
   
 

5. 在C#專案中新增一class 'NHibernateHelper'

namespace [專案名]
{
    public class NHibernateHelper
    {
        private static ISessionFactory _sessionFactory;
        private static ISessionFactory SessionFactory
        {
            get
            {
                if (_sessionFactory == null)
                {
                    var configuration = new Configuration();
                    configuration.Configure();
                    _sessionFactory = configuration.BuildSessionFactory();
                }
                return _sessionFactory;
            }
        }
        public static ISession OpenSession()
        {
            return SessionFactory.OpenSession();
        }
    }
}
完成以上設置即可開始實作相關DB存取程式

2015年5月15日 星期五

[C#] Auto load x86/x64 SQLite DLL for Platform

  How to Reference Platform Specific (X86/x64) Assemblies in Visual Studio Projects


Unload對想要有此功能之Project,Edit xxx.csproj,新增下方內容


<!--Start Code-->
<choose></choose>
<when condition="$(Platform) == 'x64'"></when>
<itemgroup></itemgroup><br />
<reference include="System.Data.SQLite, Version=1.0.65.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=AMD64"></reference>
<specificversion>False</specificversion>
<hintpath>..SQLiteX64System.Data.SQLite.DLL</hintpath>
<otherwise></otherwise>
<itemgroup></itemgroup>
<reference include="System.Data.SQLite, Version=1.0.65.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86"></reference>
<specificversion>False</specificversion>
<hintpath>..SQLiteX86System.Data.SQLite.DLL</hintpath>
<!--End Code-->

存檔後關閉,然後Reload此Project,選擇Platform (此時Reference的DLL路徑已經修正成你所選定的平台對應的DLL),Rebuild即可。

2015年4月21日 星期二

[C#] Post-Build event command line

Copy file(s) from one project to another using post build event

Visual Studio provides customized cmd script functions after Building solution.


Project->Property->Build Events->Post-Build event command line:


copy "$(SolutionDir)xxx.xml" "$(TargetDir)xxx.xml"


如上設置,可指定將Solotion Item的檔案一同複製一份至Project out folder

[C#] Set version for all projects in one solution

C# version for all projects in one solution


  1. Create an new "VersionInfo.cs" in solution item
  2. Clear its content
  3. Copy content of  one of your "AssemblyInfo.cs" in your project to "VersionInfo.cs" (Modify the content if you want)
  4. Clear content of all "AssemblyInfo.cs" in all projects
  5. Add Link to the "VersionInfo.cs" in each of your projects

2015年4月15日 星期三

VIM Settings for Visual Studio 2008

Vi Tool

ViEmu (Install-ViEmuVS-3.0.6.exe)

Key Binding
Tool->Option->General->KeyBoard 設定

View.NavigateBackward (Ctrl + T)
Edit.GoToDefinition (Ctrl+])

Theme

Tool->Import and Export Settings 設定

coding-instinct-theme_vs2008.vssettings (網路上找尋自己想要的)

2015年3月4日 星期三

使用PowerShell設定系統變數與ps1範例

以下ps1範例包含系統時間格式、程式(標準/錯誤)輸出重導、外部程式呼叫與ps1變數使用

$datestr = Get-Date -format yyyMMdd
$reporttype = "COMP"
$folder = "D:\reportFolder\"
$toolpath = "D:\scripts\Report.php"

$log = '{0}logs\{1}_{2}.log' -f $folder, $reporttype, $datestr
$err = '{0}logs\{1}_{2}.err' -f $folder, $reporttype, $datestr
php.exe $toolpath type=$reporttype folder=$folder >>$log 2>>$err

PowerShell ps1之語法遠優於舊有的bat,使用上更為便利與靈活、更為易讀。


直接透過PowerShell永久設定系統環境變數之指令
# example of adding a path to PATH
[System.Environment]::SetEnvironmentVariable("PATH", $Env:Path + ";C:\Program Files (x86)\PHP", "Machine")

# creates “myY” of category “User”, and set the value to “"la la"”
[Environment]::SetEnvironmentVariable("myY", "la la", "User")

# removing a env var from registry
[Environment]::SetEnvironmentVariable("myY", $null, "User")

# show value of “path”
$env:path

2014年6月10日 星期二

[C#] Windows Service Code Debug method

C# service Debug 方法 原始出處

個人採用方法三

只需加一些程式碼判斷與獨立使用即可!


在主要Service程式中獨立的 Start 與 Stop
public void Start(string[] args)
{
    this.OnStart(args);
}

public void Stop()
{
    this.OnStop();
}  

Program.cs中需新增的判斷
 if (Environment.UserInteractive)
 {
     Service1 s = new Service1();
     s.Start(null);
     Console.WriteLine("服務已啟動,請按下 Enter 鍵關閉服務...");
    // 必須要透過 Console.ReadLine(); 先停止程式執行
    // 因為 Windows Service 大多是利用多 Thread 或 Timer 執行長時間的工作
    // 所以雖然主執行緒停止執行了,但服務中的執行緒已經在運行了!
    Console.ReadLine();
    s.Stop();

    Console.WriteLine("服務已關閉");
 }
 else
{
   //原有程式碼
}

最後在專案的property裡設置輸出類型(Output Type),選擇 
Console Application

Google Finance API (目前已停止更新,終止服務時間未定)

Google Finance API
兩種方式,第一種較詳細
http://www.google.com/finance/info?infotype=infoquoteall&q=2311
http://finance.google.com/finance/info?client=ig&q=TPE:2311

q=後面接股票代碼,格式可以為
q=TPE:2311
q=2311

{
"id": "674465"
,"t" : "2330"                               股票代號
,"e" : "TPE"
,"l" : "122.50"
,"l_fix" : "122.50"
,"l_cur" : "NT$122.50"             成交價格
,"s": "0"
,"ltt":"1:30PM GMT+8"            時間
,"lt" : "Jun 6, 1:30PM GMT+8"
,"lt_dts" : "2014-06-06T13:30:08Z"
,"c" : "-0.50"                             漲跌
,"c_fix" : "-0.50"
,"cp" : "-0.41"                           漲幅
,"cp_fix" : "-0.41"
,"ccol" : "chr"
,"pcls_fix" : "123"                     昨收
,"eo" : ""
,"delay": ""
,"op" : "123.50"                       開盤價
,"hi" : "123.50"                        最高
,"lo" : "122.00"                        最低
,"vo" : "19.28M"
,"avvo" : ""
,"hi52" : "124.00"
,"lo52" : "92.90"
,"mc" : "3.18T"
,"pe" : "16.17"
,"fwpe" : ""
,"beta" : ""
,"eps" : "7.58"
,"shares" : "25.93B"
,"inst_own" : ""
,"name" : "Taiwan Semiconductor Mfg. Co. Ltd."  公司名稱
,"type" : "Company"
}


原始出處

[PHP] Multi-Dimension array sorting Method

Multi-Dimension array sorting Method,多微陣列排序方法 (網路上找到的)

function aasort (&$array, $key) {
    $sorter=array();
    $ret=array();
    reset($array);
    foreach ($array as $ii => $va) {
        $sorter[$ii]=$va[$key];
    }
    asort($sorter);
    foreach ($sorter as $ii => $va) {
        $ret[$ii]=$array[$ii];
    }
    $array=$ret;
}

aasort($your_array,"order");

[PHP] Array to File (File back to Array)

PHP中將Array寫入檔案,以便日後讀出的方法

寫入檔案前,需先做serialize的處理
$string = serialize($arr);
file_put_contents('File.txt', $string);

讀出檔案後,需做unserialize的處理
$str = file_get_contents('serialtest.txt');
$arr = unserialize($str);

搜尋此網誌