.
diff --git a/src/mididevice.cpp b/src/mididevice.cpp
index f20cefc..a813557 100644
--- a/src/mididevice.cpp
+++ b/src/mididevice.cpp
@@ -325,9 +325,131 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators(); nTG++) {
if (m_ChannelMap[nTG] == ucSysExChannel || m_ChannelMap[nTG] == OmniMode) {
LOGNOTE("MIDI-SYSEX: channel: %u, len: %u, TG: %u",m_ChannelMap[nTG],nLength,nTG);
+
HandleSystemExclusive(pMessage, nLength, nCable, nTG);
if (nLength == 5) {
break; // Send dump request only to the first TG that matches the MIDI channel requested via the SysEx message device ID
+ }
+
+ // Check for TX216/TX816 style performance sysex messages
+
+ if (pMessage[3] == 0x04)
+ {
+ // TX816/TX216 Performance SysEx message
+ uint8_t mTG = pMessage[2] & 0x0F; // mTG = module/tone generator number (0-7)
+ uint8_t par = pMessage[4];
+ uint8_t val = pMessage[5];
+
+ // For parameter 1 (Set MIDI Channel), only process for the TG with the number in pMessage[2]
+ if (par == 1) {
+ if (nTG != mTG) continue;
+ } else {
+ // For all other parameters, process for all TGs listening on the MIDI channel mTG or OmniMode
+ if (!(m_ChannelMap[nTG] == mTG || m_ChannelMap[nTG] == OmniMode)) continue;
+ }
+ LOGNOTE("MIDI-SYSEX: Assuming TX216/TX816 style performance sysex message because 4th byte is 0x04");
+
+ switch (par)
+ {
+ case 1: // MIDI Channel
+ LOGNOTE("MIDI-SYSEX: Set TG%d to MIDI Channel %d", mTG, val & 0x0F);
+ m_pSynthesizer->SetMIDIChannel(val & 0x0F, mTG);
+ break;
+ case 2: // Poly/Mono
+ LOGNOTE("MIDI-SYSEX: Set Poly/Mono %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setMonoMode(val ? true : false, nTG);
+ break;
+ case 3: // Pitch Bend Range
+ LOGNOTE("MIDI-SYSEX: Set Pitch Bend Range %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setPitchbendRange(val, nTG);
+ break;
+ case 4: // Pitch Bend Step
+ LOGNOTE("MIDI-SYSEX: Set Pitch Bend Step %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setPitchbendStep(val, nTG);
+ break;
+ case 5: // Portamento Time
+ LOGNOTE("MIDI-SYSEX: Set Portamento Time %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setPortamentoTime(val, nTG);
+ break;
+ case 6: // Portamento/Glissando
+ LOGNOTE("MIDI-SYSEX: Set Portamento/Glissando %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setPortamentoGlissando(val, nTG);
+ break;
+ case 7: // Portamento Mode
+ LOGNOTE("MIDI-SYSEX: Set Portamento Mode %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setPortamentoMode(val, nTG);
+ break;
+ case 9: // Mod Wheel Sensitivity
+ {
+ int scaled = (val * 99) / 15;
+ LOGNOTE("MIDI-SYSEX: Set Mod Wheel Sensitivity %d to %d (scaled %d)", nTG, val & 0x0F, scaled);
+ m_pSynthesizer->setModWheelRange(scaled, nTG);
+ }
+ break;
+ case 10: // Mod Wheel Assign
+ LOGNOTE("MIDI-SYSEX: Set Mod Wheel Assign %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setModWheelTarget(val, nTG);
+ break;
+ case 11: // Foot Controller Sensitivity
+ {
+ int scaled = (val * 99) / 15;
+ LOGNOTE("MIDI-SYSEX: Set Foot Controller Sensitivity %d to %d (scaled %d)", nTG, val & 0x0F, scaled);
+ m_pSynthesizer->setFootControllerRange(scaled, nTG);
+ }
+ break;
+ case 12: // Foot Controller Assign
+ LOGNOTE("MIDI-SYSEX: Set Foot Controller Assign %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setFootControllerTarget(val, nTG);
+ break;
+ case 13: // Aftertouch Sensitivity
+ {
+ int scaled = (val * 99) / 15;
+ LOGNOTE("MIDI-SYSEX: Set Aftertouch Sensitivity %d to %d (scaled %d)", nTG, val & 0x0F, scaled);
+ m_pSynthesizer->setAftertouchRange(scaled, nTG);
+ }
+ break;
+ case 14: // Aftertouch Assign
+ LOGNOTE("MIDI-SYSEX: Set Aftertouch Assign %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setAftertouchTarget(val, nTG);
+ break;
+ case 15: // Breath Controller Sensitivity
+ {
+ int scaled = (val * 99) / 15;
+ LOGNOTE("MIDI-SYSEX: Set Breath Controller Sensitivity %d to %d (scaled %d)", nTG, val & 0x0F, scaled);
+ m_pSynthesizer->setBreathControllerRange(scaled, nTG);
+ }
+ break;
+ case 16: // Breath Controller Assign
+ LOGNOTE("MIDI-SYSEX: Set Breath Controller Assign %d to %d", nTG, val & 0x0F);
+ m_pSynthesizer->setBreathControllerTarget(val, nTG);
+ break;
+ case 26: // Audio Output Level Attenuator
+ {
+ LOGNOTE("MIDI-SYSEX: Set Audio Output Level Attenuator %d to %d", nTG, val & 0x0F);
+ // Example: F0 43 10 04 1A 00 F7 to F0 43 10 04 1A 07 F7
+ unsigned attenVal = val & 0x07;
+ // unsigned newVolume = (unsigned)(127.0 * pow(attenVal / 7.0, 2.0) + 0.5); // Logarithmic mapping
+ // But on the T816, there is an exponential (not logarithmic!) mapping, and 0 results in the same volume as 1:
+ // 7=127, 6=63, 5=31, 4=15, 3=7, 2=3, 1=1, 0=1
+ unsigned newVolume = (attenVal == 0) ? 0 : (127 >> (7 - attenVal));
+ if (newVolume == 0) newVolume = 1; // 0 is like 1 to avoid silence
+ m_pSynthesizer->SetVolume(newVolume, nTG);
+ }
+ break;
+ case 64: // Master Tuning
+ LOGNOTE("MIDI-SYSEX: Set Master Tuning");
+ // TX812 scales from -75 to +75 cents.
+ m_pSynthesizer->SetMasterTune(maplong(val, 1, 127, -37, 37), nTG); // Would need 37.5 here, due to wrong constrain on dexed_synth module?
+ break;
+ default:
+ // Unknown or unsupported parameter
+ LOGNOTE("MIDI-SYSEX: Unknown parameter %d for TG %d", par, nTG);
+ break;
+ }
+ }
+ else
+ {
+ HandleSystemExclusive(pMessage, nLength, nCable, nTG);
}
}
}
@@ -429,7 +551,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
case MIDI_CC_SOSTENUTO:
m_pSynthesizer->setSostenuto (pMessage[2] >= 64, nTG);
break;
-
+
case MIDI_CC_PORTAMENTO:
m_pSynthesizer->setPortamentoMode (pMessage[2] >= 64, nTG);
break;
@@ -437,7 +559,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
case MIDI_CC_HOLD2:
m_pSynthesizer->setHoldMode (pMessage[2] >= 64, nTG);
break;
-
+
case MIDI_CC_RESONANCE:
m_pSynthesizer->SetResonance (maplong (pMessage[2], 0, 127, 0, 99), nTG);
break;
@@ -453,11 +575,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
case MIDI_CC_DETUNE_LEVEL:
if (pMessage[2] == 0)
{
- // "0 to 127, with 0 being no celeste (detune) effect applied at all."
+ // 0 to 127, with 0 being no detune effect applied at all
m_pSynthesizer->SetMasterTune (0, nTG);
}
else
{
+ // Scale to -99 to +99 cents
m_pSynthesizer->SetMasterTune (maplong (pMessage[2], 1, 127, -99, 99), nTG);
}
break;
@@ -592,10 +715,12 @@ bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval)
if (ucCC == MIDISystemCCMap[m_nMIDISystemCCDetune][tg]) {
if (ucCCval == 0)
{
+ // 0 to 127, with 0 being no detune effect applied at all
m_pSynthesizer->SetMasterTune (0, tg);
}
else
{
+ // Scale to -99 to +99 cents
m_pSynthesizer->SetMasterTune (maplong (ucCCval, 1, 127, -99, 99), tg);
}
return true;
@@ -664,62 +789,6 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL
case -11:
LOGERR("Unknown SysEx message.");
break;
- case 64:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setMonoMode(pMessage[5],nTG);
- break;
- case 65:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setPitchbendRange(pMessage[5],nTG);
- break;
- case 66:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setPitchbendStep(pMessage[5],nTG);
- break;
- case 67:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setPortamentoMode(pMessage[5],nTG);
- break;
- case 68:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setPortamentoGlissando(pMessage[5],nTG);
- break;
- case 69:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setPortamentoTime(pMessage[5],nTG);
- break;
- case 70:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setModWheelRange(pMessage[5],nTG);
- break;
- case 71:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setModWheelTarget(pMessage[5],nTG);
- break;
- case 72:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setFootControllerRange(pMessage[5],nTG);
- break;
- case 73:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setFootControllerTarget(pMessage[5],nTG);
- break;
- case 74:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setBreathControllerRange(pMessage[5],nTG);
- break;
- case 75:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setBreathControllerTarget(pMessage[5],nTG);
- break;
- case 76:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setAftertouchRange(pMessage[5],nTG);
- break;
- case 77:
- LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
- m_pSynthesizer->setAftertouchTarget(pMessage[5],nTG);
- break;
case 100:
// load sysex-data into voice memory
LOGDBG("One Voice bulk upload");
diff --git a/src/minidexed.cpp b/src/minidexed.cpp
index d63666c..51e6444 100644
--- a/src/minidexed.cpp
+++ b/src/minidexed.cpp
@@ -1305,6 +1305,10 @@ void CMiniDexed::ProcessSound (void)
unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail ();
if (nFrames >= m_nQueueSizeFrames/2)
{
+ // only process the minimum number of frames (== chunksize / 2)
+ // as the tg_mixer cannot process more
+ nFrames = m_nQueueSizeFrames / 2;
+
if (m_bProfileEnabled)
{
m_GetChunkTimer.Start ();
diff --git a/src/uimenu.cpp b/src/uimenu.cpp
index 1475342..730f320 100644
--- a/src/uimenu.cpp
+++ b/src/uimenu.cpp
@@ -1098,12 +1098,14 @@ string CUIMenu::GetOPValueString (unsigned nOPParameter, int nValue)
string CUIMenu::ToVolume (int nValue)
{
- static const size_t MaxChars = CConfig::LCDColumns-2;
- char VolumeBar[MaxChars+1];
- memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character
- VolumeBar[nValue * MaxChars / 127] = '\0';
-
- return VolumeBar;
+ constexpr size_t NumSquares = 14;
+ char VolumeBar[NumSquares + 1];
+ size_t filled = (nValue * NumSquares + 63) / 127;
+ for (size_t i = 0; i < NumSquares; ++i) {
+ VolumeBar[i] = (i < filled) ? (char)0xFF : '.';
+ }
+ VolumeBar[NumSquares] = '\0';
+ return VolumeBar;
}
string CUIMenu::ToPan (int nValue)
@@ -1394,11 +1396,11 @@ void CUIMenu::PgmUpDownHandler (TMenuEvent Event)
|| voiceName == "----------"
|| voiceName == "~~~~~~~~~~" )
{
- if (Event == MenuEventPgmUp) {
- PgmUpDownHandler (MenuEventPgmUp);
+ if (Event == MenuEventStepUp) {
+ PgmUpDownHandler (MenuEventStepUp);
}
- if (Event == MenuEventPgmDown) {
- PgmUpDownHandler (MenuEventPgmDown);
+ if (Event == MenuEventStepDown) {
+ PgmUpDownHandler (MenuEventStepDown);
}
}
}
@@ -2043,6 +2045,15 @@ void CUIMenu::EditMasterVolume(CUIMenu *pUIMenu, TMenuEvent Event)
default:
return;
}
- std::string valueStr = ToVolume(pUIMenu->m_pMiniDexed->GetMasterVolume127());
- pUIMenu->m_pUI->DisplayWrite("Master Volume", "", valueStr.c_str(), nValue > rParam.Minimum, nValue < rParam.Maximum);
+ unsigned lcdCols = pUIMenu->m_pConfig->GetLCDColumns();
+ unsigned barLen = (lcdCols > 2) ? lcdCols - 2 : 0;
+ std::string valueStr(barLen, '.');
+ if (barLen > 0) {
+ size_t filled = (nValue * barLen + 63) / 127;
+ for (unsigned i = 0; i < barLen; ++i) {
+ if (i < filled) valueStr[i] = (char)0xFF;
+ }
+ }
+ // Do NOT add < or > here; let DisplayWrite handle it
+ pUIMenu->m_pUI->DisplayWrite("Master Volume", "", valueStr.c_str(), true, true);
}
diff --git a/updater.py b/updater.py
index bc33058..69d53c6 100644
--- a/updater.py
+++ b/updater.py
@@ -87,11 +87,61 @@ def extract_zip(zip_path):
zip_ref.extractall(extract_path)
return extract_path
+# Function to download the latest release asset using GitHub API
+def download_latest_release_github_api(release_type):
+ """
+ release_type: 'latest' or 'continuous'
+ Returns: path to downloaded zip or None
+ """
+ import json
+ headers = {'Accept': 'application/vnd.github.v3+json'}
+ repo = 'probonopd/MiniDexed'
+ if release_type == 'latest':
+ api_url = f'https://api.github.com/repos/{repo}/releases/latest'
+ resp = requests.get(api_url, headers=headers)
+ if resp.status_code != 200:
+ print(f"GitHub API error: {resp.status_code}")
+ return None
+ release = resp.json()
+ assets = release.get('assets', [])
+ elif release_type == 'continuous':
+ api_url = f'https://api.github.com/repos/{repo}/releases'
+ resp = requests.get(api_url, headers=headers)
+ if resp.status_code != 200:
+ print(f"GitHub API error: {resp.status_code}")
+ return None
+ releases = resp.json()
+ release = next((r for r in releases if 'continuous' in (r.get('tag_name','')+r.get('name','')).lower()), None)
+ if not release:
+ print("No continuous release found.")
+ return None
+ assets = release.get('assets', [])
+ else:
+ print(f"Unknown release type: {release_type}")
+ return None
+ asset = next((a for a in assets if a['name'].startswith('MiniDexed') and a['name'].endswith('.zip')), None)
+ if not asset:
+ print("No MiniDexed*.zip asset found in release.")
+ return None
+ url = asset['browser_download_url']
+ print(f"Downloading asset: {asset['name']} from {url}")
+ resp = requests.get(url, stream=True)
+ if resp.status_code == 200:
+ zip_path = os.path.join(TEMP_DIR, asset['name'])
+ with open(zip_path, 'wb') as f:
+ for chunk in resp.iter_content(chunk_size=8192):
+ f.write(chunk)
+ return zip_path
+ print(f"Failed to download asset: {resp.status_code}")
+ return None
+
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="MiniDexed Updater")
parser.add_argument("-v", action="store_true", help="Enable verbose FTP debug output")
parser.add_argument("--ip", type=str, help="IP address of the device to upload to (skip mDNS discovery)")
parser.add_argument("--version", type=int, choices=[1,2,3], help="Version to upload: 1=Latest official, 2=Continuous, 3=Local build (skip prompt)")
+ parser.add_argument("--pr", type=str, help="Pull request number or URL to fetch build artifacts from PR comment")
+ parser.add_argument("--github-token", type=str, help="GitHub personal access token for downloading PR artifacts (optional, can also use GITHUB_TOKEN env var)")
args = parser.parse_args()
import time
@@ -100,6 +150,9 @@ if __name__ == "__main__":
local_kernel_imgs = [f for f in os.listdir(local_kernel_dir) if f.startswith('kernel') and f.endswith('.img')]
has_local_build = len(local_kernel_imgs) > 0
+ # Get GitHub token from argument or environment
+ github_token = args.github_token or os.environ.get("GITHUB_TOKEN")
+
# Ask user which release to download (numbered choices)
release_options = [
("Latest official release", "https://github.com/probonopd/MiniDexed/releases/expanded_assets/latest"),
@@ -118,61 +171,137 @@ if __name__ == "__main__":
print("Which release do you want to update?")
for idx, (desc, _) in enumerate(release_options):
print(f" [{idx+1}] {desc}")
+ print(" [PR] Pull request build (enter PR number or URL)")
while True:
- choice = input(f"Enter the number of your choice (1-{len(release_options)}): ").strip()
+ choice = input(f"Enter the number of your choice (1-{len(release_options)}) or PR number: ").strip()
if choice.isdigit() and 1 <= int(choice) <= len(release_options):
selected_idx = int(choice)-1
github_url = release_options[selected_idx][1]
+ args.pr = None
+ break
+ # Accept PR number, #NNN, or PR URL
+ pr_match = re.match(r'^(#?\d+|https?://github.com/[^/]+/[^/]+/pull/\d+)$', choice)
+ if pr_match:
+ args.pr = choice
+ selected_idx = None
+ github_url = None
break
- print("Invalid selection. Please enter a valid number.")
+ print("Invalid selection. Please enter a valid number or PR number/URL.")
# If local build is selected, skip all GitHub/zip logic and do not register cleanup
use_local_build = has_local_build and selected_idx == len(release_options)-1
- if use_local_build:
+ if args.pr:
+ # Extract PR number from input (accepts URL, #899, or 899)
+ import re
+ pr_input = args.pr.strip()
+ m = re.search(r'(\d+)$', pr_input)
+ if not m:
+ print(f"Could not parse PR number from: {pr_input}")
+ sys.exit(1)
+ pr_number = m.group(1)
+ # Fetch PR page HTML
+ pr_url = f"https://github.com/probonopd/MiniDexed/pull/{pr_number}"
+ print(f"Fetching PR page: {pr_url}")
+ resp = requests.get(pr_url)
+ if resp.status_code != 200:
+ print(f"Failed to fetch PR page: {resp.status_code}")
+ sys.exit(1)
+ html = resp.text
+ # Find all 'Build for testing' artifact blocks (look for Build for testing: ...
)
+ import html as ihtml
+ import re
+ pattern = re.compile(r'Build for testing:(.*?)Use at your own risk\.', re.DOTALL)
+ matches = pattern.findall(html)
+ if not matches:
+ print("No build artifact links found in PR comment.")
+ sys.exit(1)
+ last_block = matches[-1]
+ # Find all artifact links in the last block
+ link_pattern = re.compile(r'([^<]+)')
+ links = link_pattern.findall(last_block)
+ if not links:
+ print("No artifact links found in PR comment block.")
+ sys.exit(1)
+ # Download both 32bit and 64bit artifacts if present
+ artifact_paths = []
+ for url, name in links:
+ print(f"Downloading artifact: {name} from {url}")
+ # Try to extract the artifact ID from the URL
+ m = re.search(r'/artifacts/(\d+)', url)
+ if m and github_token:
+ artifact_id = m.group(1)
+ api_url = f"https://api.github.com/repos/probonopd/MiniDexed/actions/artifacts/{artifact_id}/zip"
+ headers = {
+ "Authorization": f"Bearer {github_token}",
+ "Accept": "application/vnd.github.v3+json",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
+ }
+ resp = requests.get(api_url, stream=True, headers=headers)
+ if resp.status_code == 200:
+ local_path = os.path.join(TEMP_DIR, name + ".zip")
+ with open(local_path, 'wb') as f:
+ for chunk in resp.iter_content(chunk_size=8192):
+ f.write(chunk)
+ artifact_paths.append(local_path)
+ else:
+ print(f"Failed to download artifact {name} via GitHub API: {resp.status_code}")
+ print(f"Request headers: {resp.request.headers}")
+ print(f"Response headers: {resp.headers}")
+ print(f"Response URL: {resp.url}")
+ print(f"Response text (first 500 chars): {resp.text[:500]}")
+ else:
+ # Fallback to direct link if no artifact ID or no token
+ headers = {}
+ if github_token:
+ headers["Authorization"] = f"Bearer {github_token}"
+ headers["Accept"] = "application/octet-stream"
+ headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
+ resp = requests.get(url, stream=True, headers=headers)
+ if resp.status_code == 200:
+ local_path = os.path.join(TEMP_DIR, name + ".zip")
+ with open(local_path, 'wb') as f:
+ for chunk in resp.iter_content(chunk_size=8192):
+ f.write(chunk)
+ artifact_paths.append(local_path)
+ else:
+ print(f"Failed to download artifact {name}: {resp.status_code}")
+ print(f"Request headers: {resp.request.headers}")
+ print(f"Response headers: {resp.headers}")
+ print(f"Response URL: {resp.url}")
+ print(f"Response text (first 500 chars): {resp.text[:500]}")
+ if not github_token:
+ print("You may need to provide a GitHub personal access token using --github-token or the GITHUB_TOKEN environment variable.")
+ if not artifact_paths:
+ print("No artifacts downloaded.")
+ sys.exit(1)
+ # Extract all downloaded zips
+ extract_paths = []
+ for path in artifact_paths:
+ ep = extract_zip(path)
+ print(f"Extracted {path} to {ep}")
+ extract_paths.append(ep)
+ # Use the first extracted path for further logic (or merge as needed)
+ extract_path = extract_paths[0] if extract_paths else None
+ use_local_build = False
+ elif use_local_build:
# Remove cleanup function if registered
atexit.unregister(cleanup_temp_files)
print("Using local build: src/kernel*.img will be uploaded.")
extract_path = None
else:
- # Use the selected GitHub URL for release
- def get_release_url(github_url):
- print(f"Fetching release page: {github_url}")
- response = requests.get(github_url)
- print(f"HTTP status code: {response.status_code}")
- if response.status_code == 200:
- print("Successfully fetched release page. Scanning for MiniDexed*.zip links...")
- # Find all tags with a MiniDexed*.zip
- pattern = re.compile(r']+href=["\']([^"\']+\.zip)["\'][^>]*>\s*]*class=["\']Truncate-text text-bold["\'][^>]*>(MiniDexed[^<]*?\.zip)', re.IGNORECASE)
- matches = pattern.findall(response.text)
- print(f"Found {len(matches)} candidate .zip links.")
- for href, filename in matches:
- print(f"Examining link: href={href}, filename={filename}")
- if filename.startswith("MiniDexed") and filename.endswith(".zip"):
- if href.startswith('http'):
- print(f"Selected direct link: {href}")
- return href
- else:
- full_url = f"https://github.com{href}"
- print(f"Selected relative link, full URL: {full_url}")
- return full_url
- print("No valid MiniDexed*.zip link found.")
- else:
- print(f"Failed to fetch release page. Status code: {response.status_code}")
- return None
-
- latest_release_url = get_release_url(github_url)
- if latest_release_url:
- print(f"Release URL: {latest_release_url}")
- zip_path = download_latest_release(latest_release_url)
- if zip_path:
- print(f"Downloaded to: {zip_path}")
- extract_path = extract_zip(zip_path)
- print(f"Extracted to: {extract_path}")
- else:
- print("Failed to download the release.")
- sys.exit(1)
+ # Use GitHub API instead of HTML parsing
+ if selected_idx == 0:
+ zip_path = download_latest_release_github_api('latest')
+ elif selected_idx == 1:
+ zip_path = download_latest_release_github_api('continuous')
+ else:
+ zip_path = None
+ if zip_path:
+ print(f"Downloaded to: {zip_path}")
+ extract_path = extract_zip(zip_path)
+ print(f"Extracted to: {extract_path}")
else:
- print("Failed to get the release URL.")
+ print("Failed to download the release.")
sys.exit(1)
# Ask user if they want to update Performances (default no)