2023年5月3日 星期三

yt-dlp 開發筆記 @ Python3, macOS 13.2.1

yt-dlp 是知名的網路影音網站研究的專案,內含不少各大網站的爬蟲技術。若某個服務失效想要貢獻的話,可這樣操作,邊改 code 變測試:

% git clone https://github.com/yt-dlp/yt-dlp.git
% cd yt-dlp
% python3 yt_dlp/__main__.py 'URL' --skip-download -j

以 2023/08/31 即將關站的 Xuite 為例,從 github.com/yt-dlp/yt-dlp/tree/master/yt_dlp/extractor/xuite.py 找一則 unit test 來跑:

% python3 yt_dlp/__main__.py 'http://vlog.xuite.net/embed/cE1xbENoLTI3NDQ3MzM2LmZsdg==?ar=0&as=0' -f mp4 --skip-download -j | jq '' | tail -n 25
  "format_id": "720",
  "height": 720,
  "protocol": "http",
  "resolution": "720p",
  "dynamic_range": "SDR",
  "aspect_ratio": null,
  "http_headers": {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.50 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-us,en;q=0.5",
    "Sec-Fetch-Mode": "navigate"
  },
  "video_ext": "mp4",
  "audio_ext": "none",
  "format": "720 - 720p",
  "_filename": "男女平權只是口號?專家解釋約會時男生是否該幫女生付錢 (中字) [27447336].mp4",
  "filename": "男女平權只是口號?專家解釋約會時男生是否該幫女生付錢 (中字) [27447336].mp4",
  "_type": "video",
  "_version": {
    "version": "2023.03.04",
    "current_git_head": "b423b6a48",
    "release_git_head": "392389b7df7b818f794b231f14dc396d4875fbad",
    "repository": "yt-dlp/yt-dlp"
  }
}

至於,若要貢獻給 yt-dlp 社群,可以仿其架構,挑一個 extractor 來模仿,過程要繼承 InfoExtractor 來做事,例如實作 _VALID_URL 和 _real_extract(self, url) 等等,可以挑幾個實作沒那麼複雜的網站研究一下流程:

2023年4月19日 星期三

小米A3 Xiaomi Mi A3 Root 與 Restore 還原到原廠ROM的筆記 @ macOS 13.3.1




沒想到已經過了好幾年,當初花不到七千買的 Android one 手機,實在好用,試試看把小米A3 Root 一下吧!遙想十多年前,在製作 Android Image 時,還要埋個用C code包裝出 su 指令呢 XD 真的很久沒 root 手機了。整個過程稱不上快,但又覺得不會太不方便。

在進行前,依舊要老話重提:

Root 有風險,手機可能變磚又損失保固,請自行評估承擔風險,在此是對四年前的手機把玩,推論是小米A3國際版手機,產品代號 laurel_sprout 且系統沒有被限制不能 rollback,以下步驟不見得適合其他款手機或品牌。

先簡介如何還原到原廠 ROM 過程,這邊就不多提如何建置操作環境(例如 adb, fastboot 指令、手機的 bootloader 是解鎖狀態)

首先,先查看手機狀態,有個關鍵的產品代號:laurel_sprout ,這跟找尋小米A3原廠ROM有關。

% fastboot devices
############ fastboot

% fastboot getvar product
product: laurel_sprout
Finished. Total time: 0.002s

% fastboot getvar anti
anti: 0
Finished. Total time: 0.001s

% fastboot getvar all
(bootloader) crc:1
(bootloader) DP:0x0
(bootloader) parallel-download-flash:yes
(bootloader) hw-revision:10000
(bootloader) unlocked:yes
(bootloader) off-mode-charge:0
(bootloader) charger-screen-enabled:0
(bootloader) battery-soc-ok:yes
(bootloader) battery-voltage:4356
...

下載原廠ROM,網路上用 小米A3 跟 ROM 就會找到,當然,在用一下 laurel_sprout 或是小米A3國際版、全球版也能找到:


看到網址是 miui.com 就比較安心不用怕了。接著要讓手機進入 fastboot 模式,過程就是先關機,在開機時,同時按住 Power 和 Volume Down 按鍵:



進行燒機:

% cd ~/Downloads
% tar -xvf laurel_sprout_global_images_V12.0.26.0.RFQMIXM_20220808.0000.00_11.0_2292e2c0b1.tgz
% cd ~/Downloads/laurel_sprout_global_images_V12.0.26.0.RFQMIXM_11.0 
% chmod a+x ./flash_all.sh && time ./flash_all.sh

如果開機一直卡在這邊,就一樣先設法進入 fastboot 模式(先長按 Power 鍵促使關機又重開,重開時按 Power 和 Volume Down),接著一樣靠 fastboot devices 偵測到後,執行下面的指令,指定啟動位置即可排除:

% fastboot set_active a
% fastboot reboot



如此就可以順利進入到手機啟動畫面,若用時間來看,刷機(fastboot)約6-7分鐘,第一次重啟時,從 Android One 畫面到出現置中的 Mi logo 不到 30 秒,接著從 Mi logo 到進入首次啟用 Android 新機畫面要接近 3分鐘。


如此手機就還原到原廠狀態了!此時 bootloader 仍是處於解鎖狀態。

接下來,說說如何 root 手機,過程:
  1. 解開 bootloader (unlocked)
    • % fastboot flashing unlock
    • % fastboot flashing unlock_critical
  2. 打開開發者模式,也啟用 USB偵錯
  3. 下載原廠 ROM - laurel_sprout_global_images_V12.0.26.0.RFQMIXM_20220808.0000.00_11.0_2292e2c0b1.tgz 從中取出 boot.img 來製作使用
  4. 在 github.com 下載 Magisk apk 來安裝 ,並使用 Magisk boot.img 製作新的 boot image
  5. 把手機關鍵,再次進入到 fastboot 模式,把 magisk_patched-26100_MKaXg.img 燒入 boot 區,接著重啟後,就可以有 root 權限了。
以下連續動作:

% wget https://github.com/topjohnwu/Magisk/releases/download/v26.1/Magisk-v26.1.apk  
% adb install ~/Downloads/Magisk-v26.1.apk 
% tar -tzf ~/Downloads/laurel_sprout_global_images_V12.0.26.0.RFQMIXM_20220808.0000.00_11.0_2292e2c0b1.tgz
% tar -xvf ~/Downloads/laurel_sprout_global_images_V12.0.26.0.RFQMIXM_20220808.0000.00_11.0_2292e2c0b1.tgz laurel_sprout_global_images_V12.0.26.0.RFQMIXM_11.0/images/boot.img
% adb push ~/Downloads/laurel_sprout_global_images_V12.0.26.0.RFQMIXM_11.0/images/boot.img /sdcard/





接著是使用 Magisk app 製作出新的 magisk_patched-26100_MKaXg.img ,把它取出後,等下靠 fastboot 燒錄進去:

% adb pull /storage/emulated/0/Download/magisk_patched-26100_MKaXg.img ~/Downloads/
/storage/emulated/0/Download/magisk_patched-26100_MKaXg.img: 1 file pulled, 0 skipped. 36.4 MB/s (67108864 bytes in 1.760s)

接著手機進入 fastboot 模式後,燒錄:

% fastboot devices
############ fastboot
% fastboot flash boot ~/Downloads/magisk_patched-26100_MKaXg.img 
Sending 'boot_a' (65536 KB)                        OKAY [  1.682s]
Writing 'boot_a'                                   OKAY [  0.333s]
Finished. Total time: 2.026s
% fastboot reboot
Rebooting                                          OKAY [  0.000s]
Finished. Total time: 0.000s

如此,手機重啟動後,已完成 root 了,這時單純用 adb shell 進去後打 su 則可以看到手機出現攔截的事件資訊,收工!

2023年4月3日 星期一

TOTOLINK LR350 4G LTE SIM WIFI AP 無線網路分享器 與 無框行動 Circles.Life


之前一直想要實驗,趁一些特價活動先敗了一台約 1500 台幣的 TOTOLINK LR350 支援4G SIM卡的無線分享機,他的優勢是這個價格支援全台常見的 4G 電信,其次是有設計 USB 供電。如此就可以拿來隨便移動。若單純要再低價一點,應當還有約 1200 元左右的款式可挑,但並沒有支援所有全台電信網路,使用上要留意。


在台灣,單純的手機上網+熱點分享已經很夠用了,但還有一種可能,那就是住家電信訊號不佳,必須靠 WIFI AP 的服務,如路橋旁或是樓高超過10樓時,很有可能遮蔽到電信基地台,這時,可能整個空間只有少數幾處會有可連外的訊號,如靠窗的位置等等,這時就是這類設備的好處,當然,更正確地解方還是牽網路線來解吧! XD




目前在雲林實測的效果就是靠 fast.com 和 speed.cloudflare.com 可測得的瞬間速度可以有 10Mbps 似乎堪用。

最後,一開始在臺北交叉測試時,用一隻 android phone - 小米A3 直接插 SIM 卡實測可以有 80Mbps ,但用 TOTOLINK LR350 卻只能看到 50Mbps ,比較小可惜了。另外,同一時間也申辦 台哥大 和 遠傳 辦理體驗4G網路,實際上台灣大哥大的 SIM 卡在 LR350 上沒測試成功,有顯示可以連上並取的 IP ,但一直顯示無網路,簡稱無法連外(不確定是不是七日網路體驗版的差距,也無機會驗證了)。另外,用小米A3分別實測台哥大和遠傳的速度,實際上遠傳還是比較優的,也的確符合網路上的統計資訊,全台4G電信網路平均速度的品質是 中華電 > 遠傳 > 台哥大。

而這次測試至少也驗證 TOTOLINK LR350 與 無框行動 Circles.Lift 是可以正常連線使用的,若申請 Circles.Lift 需要推薦碼,可以再用這組:LFNGFYC5


參考資料:

2023年3月29日 星期三

完整清除 Uninstall pCloud Drive app @ macOS 13.2.1


故事是這樣的,幾個月前實驗使用 Cryptomator + pCloud 備份,由於前者會將資料切個成常見的零碎的目錄結構,如果有檔案出現異常時,整個資料就無法讀取。

Error Code 4VHF:Q9JM:I1GP

最近 pCloud 很頻繁更新,再加上更新至 Cryptomator 1.7.3 ,很奇妙的,有一個高頻變動的目錄,開始出現無法讀取的現象。經過幾番追蹤後,確認是因為 Cryptomator + macFUSE 方案產生的現象,接著做檔案搬遷就會出錯,無論降低版本到 Cryptomator 1.6.17 還是升級 macFUSE 4.4.2,都沒法排除。

測試方式是建立新的加密庫,接著 rsync -avP 搬遷,皆會一陣子就出現 input/output error 。最後將 Cryptomator 更換成 WebDAV 模式後,才確定排除。且 Cryptomator 錯誤訊息介面可以快速跳到 github 4VHF:Q9JM:I1GP 相關討論區,也在講 macFUSE 的事情。排除問題後,就進入到這次的主題 pCloud app 的處理。

pCloud App Bug?沒有 synced folders 規則,還一直在同步

pCloud App Bug?沒有 synced folders 規則,添加時還顯示已在同步(已有規則)

重建完加密庫後,想要透過 pCloud app 同步資料時,發現整個 pCloud Drive app - macOS 進入到非常詭異的狀態,我只是把 pCloud app - Synced Folders 的目錄透過介面打 X 取消指定目錄同步,下一刻我發現本地的目錄被刪除了,對應的 pCloud 雲端上的資料也被刪除。這件事其實還滿嚴重的。

當初對 pCloud 的用法也是處於體驗,純粹當作另一處備份區,想說資料不見也沒差再重建就好。殊不知在本地重建時, pCloud app 一直還是不斷的 sync 指定目錄,且同步到 pCloud 的家目錄區(首頁),導致整個 pCloud 首頁大亂。

pCloud App Bug?同步上去都擺在家目錄,家目錄都亂了

我推論比較像 pCloud app 有問題,處理舊的同步規則時,似乎把所有東西都建立在 pCloud 家目錄,而非保有 Cryptomator 目錄結構:

% tree -L 2 My-Resource 
My-Resource 
├── d
│   ├── J2
│   └── XX
├── masterkey.cryptomator
├── masterkey.cryptomator.########.bkup
├── vault.cryptomator
├── vault.cryptomator.########.bkup
└── 重要.rtf

4 directories, 5 files

下一刻移除 pCloud 再重裝也是一樣,永遠都可以看到 pCloud app 很努力的在同步我剛在原處創建的 Cryptomator 加密庫,並且 pCloud 首頁永遠都是雜亂的 XD 

終於下定決心來找找,如何乾淨地移除 pCloud 設定檔,推論有一些 plist 紀錄著東西,也猜想 pCloud app 版本不同後,新舊版資料紀錄錯亂或是做了什麼相容架構,導致最新版刪不了舊版紀錄的資料,但又一直做相容架構,拿舊版的資料出來用。此時在新版 pCloud app 添加指定目錄同步時,會一直彈跳出不能重複設定的資訊(pCloud app UI 介面上可以看到,目前一條規則也沒有):Can not add new sync: Folder already syncing!

來個乾淨的刪除吧!

先把 pCloud app 從應用程式的目錄拖拉丟去垃圾桶:

% open /Applications 

接著檢查其他資料:

% sudo find /Library -iname "*pcloud*" 2>/dev/null
...
/Library/PrivilegedHelperTools/com.pcloud.pcloudfs.Mounter.Helper
/Library/Logs/DiagnosticReports/pCloud Drive_2023-##-##-#######_#####.diag
/Library/LaunchDaemons/com.pcloud.pcloudfs.Mounter.Helper.plist
...

% find ~/Library -iname "*pcloud*" 2>/dev/null
...
/Users/account/Library/WebKit/com.pcloud.pcloud.macos
/Users/account/Library/Preferences/com.pcloud.pcloud.macos.plist
/Users/account/Library/Application Scripts/com.pcloud.pcloud.macos.pCloudFinderExt
/Users/account/Library/HTTPStorages/com.pcloud.pcloud.macos.binarycookies
/Users/account/Library/Containers/com.pcloud.pcloud.macos.pCloudFinderExt
/Users/account/Library/Containers/com.pcloud.pcloud.macos.pCloudFinderExt/Data/Library/Application Scripts/com.pcloud.pcloud.macos.pCloudFinderExt
/Users/account/Library/Caches/com.pcloud.pcloud.macos

偷看 /Users/account/Library/Preferences/com.pcloud.pcloud.macos.plist 內容,他還記錄著 sync 目錄資訊。

最後就人工把以下的資料刪除:

/Library/PrivilegedHelperTools/com.pcloud.pcloudfs.Mounter.Helper
/Library/Logs/DiagnosticReports/pCloud Drive_2023-##-##-#######_#####.diag
/Library/LaunchDaemons/com.pcloud.pcloudfs.Mounter.Helper.plist
/Users/account/Library/WebKit/com.pcloud.pcloud.macos
/Users/account/Library/Application Scripts/com.pcloud.pcloud.macos.pCloudFinderExt
/Users/account/Library/HTTPStorages/com.pcloud.pcloud.macos.binarycookies
/Users/account/Library/Containers/com.pcloud.pcloud.macos.pCloudFinderExt
/Users/account/Library/Caches/com.pcloud.pcloud.macos
/Users/account/Library/Preferences/com.pcloud.pcloud.macos.plist

原本以為這樣就搞定了,殊不知裝完 pCloud app 啟動後,又發現繼續同步資料。最後想起一招:從 活動監視器 那邊看 pCloud app 開啟的檔案清單



就發現有一處是 ~/.pcloud ,把他刪掉後,終於正常了。

2023年3月22日 星期三

Flutter 開發筆記 - 使用 Admob - BannerAds 版型 @ Flutter 3.7.7, Android Studio 2022.1.1 Patch2, macOS 13.2.1




首先,透過 Android Studio 創建一個專案,預設跑 Build -> Flutter -> Build APK 可以正常跑東西:

flutter build apk

💪 Building with sound null safety 💪

Running Gradle task 'assembleRelease'...                           87.4s
✓  Built build/app/outputs/flutter-apk/app-release.apk (16.8MB).
Process finished with exit code 0

但是在 Project 視窗內,對 android 目錄 -> 右鍵 -> Flutter -> Open Android module in Android Studio 則是會看到:

Unsupported Java. 
Your build is currently configured to use Java 19.0.2 and Gradle 7.5.

Possible solution:
 - Open Gradle wrapper settings, change `distributionUrl` property to use compatible Gradle version and reload the project

解法就是 File -> Project structure -> Project

Android Gradle Plugin Version: 7.4.2
Gradle Version: 7.6.1

就可以很正常搞定了。

接著處理添加 Admob 的用法,參考 developers.google.com/admob/flutter/quick-start 流程:
  1. 先到原本的 flutter 專案上,選擇 terminal -> flutter pub add google_mobile_ads ,運行一陣子後,回去看 pubspec.yaml 可以看到添加了 google_mobile_ads: ^2.3.0 項目
  2. 對 flutter 專案的 Project 視窗內的 android 目錄 -> 右鍵 -> Flutter -> Open Android module in Android Studio ,進入開發單純的 Android 專案一樣,要進行 AndroidManifest.xml 設定,主要是添加 Admob 使用的資訊

<manifest>
    <application>
        <!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-3940256099942544~3347511713"/>
    <application>
<manifest>

接著用 Build -> Rebuild Project 會看到數個錯誤訊息:
  • Cannot fit requested classes in a single dex file (# methods: 68231 > 65536) 
  • Error: uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:google_mobile_ads]
解法就是在 build.gradle (Module: app) 更新:

defaultConfig {
    // ...

    minSdk 19
    multiDexEnabled true

    // ...
}

// ...

dependencies {
    // ...

    implementation "androidx.multidex:multidex:2.0.1"

    // ...
}

接著就是 Sync 以及 Build -> Rebuild Project ,更多資訊須參考 developer.android.com/studio/build/multidex?hl=zh-tw

3. 回到 flutter 專案上,編輯 lib/main.dart

import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  MobileAds.instance.initialize();
  runApp(const MyApp());
}

// ...

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  final BannerAd myBanner = BannerAd(
    // https://developers.google.com/admob/android/test-ads?hl=zh-cn#sample_ad_units
    // https://developers.google.com/admob/ios/test-ads?hl=zh-cn#demo_ad_units
    adUnitId: Platform.isAndroid ? 'ca-app-pub-3940256099942544/6300978111' : 'ca-app-pub-3940256099942544/2934735716' ,
    size: AdSize.banner,
    request: const AdRequest(),
    listener: BannerAdListener(
      // Called when an ad is successfully received.
      onAdLoaded: (Ad ad) => print('Ad loaded.'),
      // Called when an ad request failed.
      onAdFailedToLoad: (Ad ad, LoadAdError error) {
        // Dispose the ad here to free resources.
        ad.dispose();
        print('Ad failed to load: $error');
      },
      // Called when an ad opens an overlay that covers the screen.
      onAdOpened: (Ad ad) => print('Ad opened.'),
      // Called when an ad removes an overlay that covers the screen.
      onAdClosed: (Ad ad) => print('Ad closed.'),
      // Called when an impression occurs on the ad.
      onAdImpression: (Ad ad) => print('Ad impression.'),
    ),
  );

  @override
  void initState() {
    super.initState();
    myBanner.load();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(

        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              alignment: Alignment.center,
              width: myBanner.size.width.toDouble(),
              height: myBanner.size.height.toDouble(),
              child: AdWidget(ad: myBanner),
            ),
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

更多資訊: developers.google.com/admob/flutter/banner?hl=zh-cn (英文版顯示 404 NOT Found)

以上就順玩流程,但仍有很多細節未處理,像是廣告沒 load 到,或是 App 或 View/ViewController 切換時廣告資源釋放等等。

2023年3月15日 星期三

Flutter 開發筆記 - 撰寫平台相依性功能,以取得 Android Device 資料為例


研究了一下 Flutter 如何寫平台相依性的程式碼,看一眼也是常見的 Channel or Message 等溝通機制,也滿直觀的。以 Android 平台和 Kotlin 為例,先找到 MainActivity.kt ,接著,在 Code -> Override Methods 可以找到 configureFlutterEngine 可以添加

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
    }
}

接著就可以在透過 MethodChannel 建立綁定溝通管道:

import android.os.Build
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    private val myCHANNEL = "samples.flutter.dev/helper"
    
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, myCHANNEL).setMethodCallHandler {
                call, result ->
            // This method is invoked on the main thread.
            // TODO

            if (call.method == "getBatteryLevel") {
                val batteryLevel = 0; // getBatteryLevel()
                if (batteryLevel != -1) {
                    result.success(batteryLevel)
                } else {
                    result.error("UNAVAILABLE", "Battery level not available.", null)
                }
            } else if (call.method == "getDeviceInfo") {
                result.success(getDeviceInfo())
            } else {
                result.notImplemented()
            }
        }
    }

    private fun getDeviceInfo(): HashMap<String, String> {
        val deviceInfo:HashMap<String, String> = HashMap<String, String>()
        deviceInfo.run {
            put("MODEL", Build.MODEL)
            put("MANUFACTURER", Build.MANUFACTURER)
        }
        return deviceInfo
    }
}

如此在 Flutter Dart 端,就可以呼叫:

class _MyHomePageState extends State<MyHomePage> {
  static const platform = MethodChannel('samples.flutter.dev/helper');

  String _deviceInfo = "system info unknown";
  Future<void> _getDeviceInfo() async {
    Map<String, String> deviceInfo = {};
    try {
      final Map<Object?, Object?> result = await platform.invokeMethod('getDeviceInfo');
      deviceInfo.clear();
      result.forEach((key, value) {
        if (key.runtimeType == String && value.runtimeType == String) {
          deviceInfo[key.toString()] = value.toString();
        }
      });
    } on PlatformException catch (e) {
      deviceInfo.clear();
    } on Exception catch (e) {
      //print("Exception catch: $e ");
    }

    setState(() {
      _deviceInfo = json.encode(deviceInfo);
    });
  }
...

後續就只是完善細節,強烈建議直接觀看官方文件,有完整的細流程資訊和各平台的範例: docs.flutter.dev/development/platform-integration/platform-channels 

Flutter 開發筆記 - 排除 Could not open settings generic class cache for settings file @ macOS 13.2.1, openjdk 19.0.2, Android Studio Electric Eel | 2022.1.1 Patch 2


最近工作上關係,來回顧一下 Flutter app 的開發,預計寫一款非常簡單的 App 試試水溫。結果在 macOS 13.2.1, openjdk 19.0.2, Android Studio Electric Eel | 2022.1.1 Patch 2 環境,預設弄個專案出來卻不能編譯:

Launching lib/main.dart on sdk gphone64 arm64 in debug mode...
Running Gradle task 'assembleDebug'...

FAILURE: Build failed with an exception.

* What went wrong:
Could not open settings generic class cache for settings file '/Users/UserID/AndroidStudio/usb_displayport_helper/android/settings.gradle' (/Users/UserID/.gradle/caches/7.5/scripts/XXXXXX).
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 63

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 24s
Exception: Gradle task assembleDebug failed with exit code 1

研究了一下,主因是 JDK 與 Gradle 版本問題,需要多做一些事就能排除了。記錄一下。

此時環境:

% java --version
openjdk 19.0.2 2023-01-17
OpenJDK Runtime Environment (build 19.0.2+7-44)
OpenJDK 64-Bit Server VM (build 19.0.2+7-44, mixed mode, sharing)

這時,在既有專案的左邊上方的 Project 小視窗,挑選 android 目錄,點選右鍵 -> Flutter -> Open Android module in Android Studio -> New Window ,在新的 Android Studio 還沒按 Build 就看到訊息:

Unsupported Java. 
Your build is currently configured to use Java 19.0.2 and Gradle 7.5.

Possible solution:
 - Open Gradle wrapper settings, change `distributionUrl` property to use compatible Gradle version and reload the project

這時就來更換預設的環境: File -> Project Structure -> Project

- Android Gradle Plugin Version: 7.4.2
- Gradle Version: 7.6.1

按下 OK 就會開始抓資料,最後也順利編譯後在模擬器上跑了。

2023年1月31日 星期二

[Linux] lftp Fatal error: Certificate verification @ Synology NAS

從舊機搬遷資料出來時,除了 rsync 可用之外,還有 lftp 可以試試。結果一連就踩到 Fatal error: Certificate verification 問題,所幸有設定可以排除,後續就可以靠 lftp mirror 試試

$ lftp ftp://user@my-nas-ip
lftp user@my-nas-ip:~> ls                  
ls: Fatal error: Certificate verification: unable to get local issuer certificate (##:##)

解法:

$ lftp ftp://user@my-nas-ip -e "set ssl:verify-certificate false"

此外,若 Synology NAS 機型不會太舊,搬遷方式可以多試試 Migration Assistant 服務。

[Linux] cross compile tmux for Synology DS723+ via DSM 7.1 ToolChain - AMD x86 Linux 4.4.180 (r1000)

終於想不開把家裡的 NAS 升級了,一口氣跳到 AMD Ryzen R1600 型號,接著又要找一下編譯 tmux 的方法 Orz 參考六七年前寫的這篇:[Linux] cross compile tmux for Synology DS216play via DSM 6.1 Tool Chains - STMicroelectronics Monaco Linux 3.10.102 @ Ubuntu 16.04 64bit

過程:
原本找了一台小機器 Ubuntu 18.04 編譯,結果一直出錯,最後還是找個乾淨的機器,一下就搞定。這次用 libevent-2.1.12-stable 時,先略過 openssl 編譯 ,雖然編 openssl 也沒那麼難,但就是這麼懶 XD

Docker 流水帳:

% docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
10175de2f0c4: Pull complete 
Digest: sha256:9dc05cf19a5745c33b9327dba850480dae80310972dea9b05052162e7c7f2763
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest
% docker run -it --platform linux/amd64 -p 10022:22 -p 18000:8000 ubuntu:22.04  
# apt update && apt upgrade -y && apt install -y lsb-release net-tools
# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy

# apt install -y gcc g++ make python3 tmux vim git wget xz-utils file

開工:

# cd ~/
# wget https://global.download.synology.com/download/ToolChain/toolchain/7.1-42661/AMD%20x86%20Linux%204.4.180%20%28r1000%29/r1000-gcc850_glibc226_x86_64-GPL.txz
# tar -xvf r1000-gcc850_glibc226_x86_64-GPL.txz

# cd ~/
# wget https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.4.tar.gz
# tar -xvf ncurses-6.4.tar.gz
# cd ncurses-6.4
~/ncurses-6.4# PATH=$HOME/x86_64-pc-linux-gnu/bin:$PATH CC=x86_64-pc-linux-gnu-gcc CFLAGS="-I$HOME/x86_64-pc-lix-gnu/include" ./configure --build x86_64-pc-linux-gnu --prefix=$HOME/x86_64-pc-linux-gnu/
~/ncurses-6.4# PATH=$HOME/x86_64-pc-linux-gnu/bin:$PATH make -j2 install

# cd ~/
# wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
# tar -xvf libevent-2.1.12-stable.tar.gz
# cd libevent-2.1.12-stable
~/libevent-2.1.12-stable# PATH=$HOME/x86_64-pc-linux-gnu/bin:$PATH CC=x86_64-pc-linux-gnu-gcc CFLAGS="-I$HOME/x86_64-pc-lix-gnu/include" ./configure --build x86_64-pc-linux-gnu --prefix=$HOME/x86_64-pc-linux-gnu/ --disable-openssl
~/libevent-2.1.12-stable# PATH=$HOME/x86_64-pc-linux-gnu/bin:$PATH make -j2 install

# cd ~/
# wget https://github.com/tmux/tmux/releases/download/3.3a/tmux-3.3a.tar.gz
# tar -xvf tmux-3.3a.tar.gz 
# cd tmux-3.3a
~/tmux-3.3a# PATH=$HOME/x86_64-pc-linux-gnu/bin:$PATH CC=x86_64-pc-linux-gnu-gcc CFLAGS="-I$HOME/x86_64-pc-linux-gnu/include -I$HOME/x86_64-pc-linux-gnu/include/ncurses" LDFLAGS="-L$HOME/x86_64-pc-linux-gnu/lib" ./configure --build x86_64-pc-linux-gnu --prefix=$HOME/x86_64-pc-linux-gnu/ --enable-static
~/tmux-3.3a# PATH=$HOME/x86_64-pc-linux-gnu/bin:$PATH make -j2
...
x86_64-pc-linux-gnu-gcc -std=gnu99 -O2    -I/root/x86_64-pc-linux-gnu/include -I/root/x86_64-pc-linux-gnu/include/ncurses -static  -L/root/x86_64-pc-linux-gnu/lib -o tmux alerts.o arguments.o attributes.o cfg.o client.o cmd-attach-session.o cmd-bind-key.o cmd-break-pane.o cmd-capture-pane.o cmd-choose-tree.o cmd-command-prompt.o cmd-confirm-before.o cmd-copy-mode.o cmd-detach-client.o cmd-display-menu.o cmd-display-message.o cmd-display-panes.o cmd-find-window.o cmd-find.o cmd-if-shell.o cmd-join-pane.o cmd-kill-pane.o cmd-kill-server.o cmd-kill-session.o cmd-kill-window.o cmd-list-buffers.o cmd-list-clients.o cmd-list-keys.o cmd-list-panes.o cmd-list-sessions.o cmd-list-windows.o cmd-load-buffer.o cmd-lock-server.o cmd-move-window.o cmd-new-session.o cmd-new-window.o cmd-parse.o cmd-paste-buffer.o cmd-pipe-pane.o cmd-queue.o cmd-refresh-client.o cmd-rename-session.o cmd-rename-window.o cmd-resize-pane.o cmd-resize-window.o cmd-respawn-pane.o cmd-respawn-window.o cmd-rotate-window.o cmd-run-shell.o cmd-save-buffer.o cmd-select-layout.o cmd-select-pane.o cmd-select-window.o cmd-send-keys.o cmd-server-access.o cmd-set-buffer.o cmd-set-environment.o cmd-set-option.o cmd-show-environment.o cmd-show-messages.o cmd-show-options.o cmd-show-prompt-history.o cmd-source-file.o cmd-split-window.o cmd-swap-pane.o cmd-swap-window.o cmd-switch-client.o cmd-unbind-key.o cmd-wait-for.o cmd.o colour.o control-notify.o control.o environ.o file.o format.o format-draw.o grid-reader.o grid-view.o grid.o input-keys.o input.o job.o key-bindings.o key-string.o layout-custom.o layout-set.o layout.o log.o menu.o mode-tree.o names.o notify.o options-table.o options.o paste.o popup.o proc.o regsub.o resize.o screen-redraw.o screen-write.o screen.o server-acl.o server-client.o server-fn.o server.o session.o spawn.o status.o style.o tmux.o tty-acs.o tty-features.o tty-keys.o tty-term.o tty.o utf8.o window-buffer.o window-client.o window-clock.o window-copy.o window-customize.o window-tree.o window.o xmalloc.o osdep-linux.o    compat/closefrom.o compat/fgetln.o compat/freezero.o compat/getdtablecount.o compat/getpeereid.o compat/getprogname.o compat/setproctitle.o compat/strlcat.o compat/strlcpy.o compat/strtonum.o compat/recallocarray.o compat/getopt.o compat/imsg.o compat/imsg-buffer.o compat/vis.o compat/unvis.o compat/fdforkpty.o -lutil -lncurses -levent_core -lm  -lncurses -lresolv
cmd-parse.o: In function `yylex_token_tilde':
cmd-parse.c:(.text+0x61d): warning: Using 'getpwnam' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
cmd-parse.c:(.text+0x6a9): warning: Using 'getpwuid' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/root/x86_64-pc-linux-gnu/lib/libevent_core.a(evutil.o): In function `test_for_getaddrinfo_hacks':
evutil.c:(.text+0x1836): warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/root/x86_64-pc-linux-gnu/lib/libevent_core.a(evutil.o): In function `evutil_unparse_protoname':
evutil.c:(.text+0x1272): warning: Using 'getprotobynumber' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/root/x86_64-pc-linux-gnu/lib/libevent_core.a(evutil.o): In function `evutil_parse_servname':
evutil.c:(.text+0x11ee): warning: Using 'getservbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

~/tmux-3.3a# file tmux
tmux: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, with debug_info, not stripped

~/tmux-3.3a# python3 -m http.server 
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

還記得當初跑 docker 時有把本機的 18000 port 對到裡頭的 8000 port ,這時就可以靠 http://localhost:18000/tmux 去下載 tmux 來用。

實際運行:

$ uname -a
Linux NAS 4.4.180+ #42962 SMP Thu Dec 8 21:34:11 CST 2022 x86_64 GNU/Linux synology_r1000_723+

$ ~/bin/tmux -V
tmux-tool 3.3a

另外,在 NAS 內運行時,可以多設定 shell 環境資訊:

$ cat ~/.profile 
export PATH=$HOME/bin:$PATH
export TERM=xterm

收工!

2023年1月23日 星期一

安裝 PC 版 app 舊版 Line v6.7.3.2506 @ macOS 10.13.6

之前在 Macbook Pro 13-inch, Late 2011 在 2020年重灌後,一直靠 Chrome Browser extension 用 Line 服務 (Ver 2.5.8 @ 2022/12/22 更新),今年回鄉使用時,發現很多 app icon 縮圖也怪了,開始想裝舊版 PC app 了 XD 可惜 macOS App Store 的最新架上版本已經不能裝了,要求 macOS 版本不合。

找了一下,有些論壇都在提供舊版 dmg (或 App 壓縮檔案),瞬間才想起 macOS App Store 可以去找以前下載過的版本,瞬間可以解決需求 XD 且又不用擔心網路來源不明的程式。安裝到 Line v6.7.3.2506 版本,收工!

缺點就是佔了個 100MB 系統空間。