‏הצגת רשומות עם תוויות python. הצג את כל הרשומות
‏הצגת רשומות עם תוויות python. הצג את כל הרשומות

יום ראשון, מרץ 22, 2026

אז מה אני הייתי צריך בשביל לעטוף משהוא כמו שצריך עם pyinstaller

הייתי צריך לעשות משהוא מהיר, ומלוכלך, עם סיכון קטן שאצטרך לשחרר את קוד המקור שלו.
 
לכן,  בחרתי במקום להשתמש בשפה מאוד פשוטה (C++) לבחור בפייתון. לפייתון יש המון יתרונות, אבל בשבילי גם הרבה חסרונות. השפה הזאת היא בדרך כלל לא ברירת המחדל שלי, אבל כשיש צורך והיא הכי מתאימה אז בה אני בוחר. למרות החסרות של פייתון אני השתמשתי בסט החוקים שיש אצלנו בחברה לכתיבת קוד, אז מבחינת איכות הקוד הבסיס זה בערך אותה האיכות. למה אני אומר שזה מלוכלך ? כי בשונה מהכלים הרגילים שלי ישנן תלויות שאינן עומדת בתקינה שאיתה אני בדרך כלל עובד, אבל מכיוון שפה לא הייתי צריך לעמוד באותה התקינה אז ייכלתי להרשות זאת לעצמי.
 
כרגיל לפייתון, יצא לי לכתוב את התסריט הלוגי בסדר גודל של כארבעים דקות, זה לא הרבה, וזה לא קצת, זה הזמן למטרה  כזאת. הבעיה ? יש לי המון תלויות. ועליהם אני לא עברתי היום, אבל יצא לי לעבור על הקוד של חלקם לפני מספר חודשים, על האחרים כמו pandas ט numpy  אני די בקטע של סמוך על סמוך. בשפות פשוטות (כמו C++ למשל ) אז קיימות ספרייות תקניות שמספקות את הצרכים, והן נבדקות המון, גם בקוד פתוח וגם בקוד סגור.
 
חוץ מזה, שאני משתמש בדברים יחסית מוכרים, דברים כמו pandas וnumpy ביחד עם  מספר רב של חבילות. בדר"כ זה משהוא שאני לא עושה, אני אשתמש במספר מאוד קטן של תלויות, אבל פה היה משהוא שלא דרש אימות מאוד כבד.
 
כאשר אני בונה מתסריט פייתון לקובץ exe , ואני  לא בוחר להשתמש ב cython אלא פשוט בבנייה ישירה , אני משתמש ב PyInstaller המאוד נוח. מדובר בכלי פתוח , חופשי , שעדיין לא גרם לי לפקפק בו.
 

הדברים שצריך לעשות בשביל לעטוף ולהנות מתוכנה כמו שצריך , זה קודם כל לייצר לוגו לתוכנה בפורמט png , בגדלים של 12, 16, 18,24,32,48,56,96,128 ו 256 . לעטוף הכל ע"י הכלי icotool ולעטוף הכל לתוך ico בודד. 
 
אפשר בצורה הבאה:
 
icotool -c -o logo.ico \
--index 1 ./logo_12.png \
--index 2 ./logo_16.png \
--index 3 ./logo_18.png \
--index 4 ./logo_24.png \
--index 5 ./logo_32.png \
--index 6 ./logo_48.png \
--index 7 ./logo_56.png \
--index 8 ./logo_64.png \
--index 9 ./logo_72.png \
--index 10 ./logo_96.png \
--index 11 ./logo_128.png \
--index 12 ./logo_256.png
אנחנו צריכים לבנות את הico לפני כן, כי למרות ש pyinstaller יכול להשתמש בחבילה חיצונית בשביל לעטוף לוגו, במקרה שלי הלוגו יצא ממש מכוער ומרוח, אבל שימוש ב icotool לא רק שאפשר לי מספר גדלים , אלא גם התמונה יצאה יפה יותר.

לאחר שבניתם את זה ,יש לייצר קובץ version כדוגמה:
# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
  ffi=FixedFileInfo(
    # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
    # Set not needed items to zero 0.
    filevers=(124, 81, 52, 173),
    prodvers=(124, 81, 52, 173),
    # Contains a bitmask that specifies the valid bits 'flags'
    mask=0x3f,
    # Contains a bitmask that specifies the Boolean attributes of the file.
    flags=0x0,
    # The operating system for which this file was designed.
    # 0x4 - NT and there is no need to change it.
    OS=0x40004,
    # The general type of file.
    # 0x1 - the file is an application.
    fileType=0x1,
    # The function of the file.
    # 0x0 - the function is not defined for this fileType
    subtype=0x0,
    # Creation date and time stamp.
    date=(0, 0)
    ),
  kids=[
    StringFileInfo(
      [
      StringTable(
        '040904B0',
        [StringStruct('CompanyName', 'Company Name'),
        StringStruct('FileDescription', 'heat sensor utility'),
        StringStruct('FileVersion', '124.81.52.173'),
        StringStruct('InternalName', 'heatsensor'),
        StringStruct('LegalCopyright', '(C) Foobar Inc.'),
        StringStruct('OriginalFilename', 'heatsensor.exe'),
        StringStruct('ProductName', 'Baz utility'),
        StringStruct('ProductVersion', '124.81.52')])
      ]),
    VarFileInfo([VarStruct('Translation', [1033, 1200])])
  ]
)
  
המבנה הזו הוא המקבילה של המבנה VS_VERSION_INFO שאנחנו מוסיפים כאשר בונים תוכנית בשפת C ו C++.ובדיוק כמו שם צריך לזכור ש filesver וגם FileDescription חייבים לייצג את אותו הערך !.
 
לאחר ייצור ה exe ע"י שימוש בכלי  pyinstaller אני חותם על הקובץ באמצעות signtool, כמובן שאתם צריכים חותמת בצורת pfx בה אתם יכולים להשתמש. כן בדיוק אותו הכלי שאנחנו משתמשים בו בשביל לחתום על דברים שבנינו ב C++, אין הבדל.
 
את החותמת pfx ניתן לייצר באמצעות openssl על מנת להמיר מהפורמט בה יש לכם חותמת שיכולה לבצע חתימה על קוד. האם חתימה על קוד נדרשת ? תראו , חותמת EV מקלה על החיים בהתקנה (אינני יודע כמה עולה) , אבל נוחה יותר, חותמת selfsigned ע"י מישהוא שאתם מכירים ? לדעתי שווה גם כן. אבל זה יותר נראות מאשר כל דבר אחר לדעתי היום. 

יום שישי, יולי 21, 2023

Taking a website screenshot with selenium

מצאתי שקוד ישן שלי שנועד לבדוק דף שמכיל קצת JS מעצבן, כבר לא עובד אחרי ששדרגתי את הדביאן שלי. הקוד כתוב בפייתון כי הייתי צריך לייצר קוד דחוף בזמנו וכל שאר הדברים כבר היו בפייתון, וכן לא יכלתי להשתמש בשפה נורמאלית כמו פרל (סליחה לשי, הוא עשה פרצוף כשסיפרתי לו מקרה דומה בעבר). מפה לשם זה נשאר כבר שנים ככה, וזה פשוט עובד.
 
היום ניסתי לבדוק אותו כי שוב יש לי גישה למערכת שאתה מייצרת אותו (embeded device), מתברר שמאז bulseye  משהוא נשבר , והסקריפט היה מתעופף כשיש גרסה עדנית של סלניום.
 
התיקון הוא שימוש בdriver.save_screenshot במקום הגישה שהייתה קודם לכן בה הייתי מצלם מסך ע"י קריאה למתודה על האלמנט עצמו.
 
הקוד הזה הוא גרוע, הוא לא בודק שגיאות או חריגות, אבל הוא עובד לצרכים שלי.


#!/usr/bin/python3
#Author Boris Shtrasman 2020
#License MIT/X11
# Originally a short script to take a screenshot from my dump device web interface, later adjust to any scroll required page, all it does, is simply a screengrab and not a proper PDF. as the device has no auth just pass url with full path.
# Tested on Debian Bookworm in 2023, you can install some required debs to be able to run it:
#   apt-get install python3-selenium chromium-driver chromium-l10n chromium-shell chromium-driver chromium chromium-sandbox --no-install-recommends 


import argparse #in order to be able to use argument parser
from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

import time
import datetime

parser =argparse.ArgumentParser()
parser.add_argument('-u','--url', help="url to fetch", required= True)
args = parser.parse_args()
url = args.url
print (url)
cur_date = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
outputfilename = '/tmp/' + url.replace('/','_') +'_' + cur_date + '.png'

#Path to chrome driver, check your OS in case of a non ddebian one
CHROMEDRIVER = '/usr/bin/chromedriver'
#why did I choose a log-path instead a proper logger ? because I'm lazy, that's why!
s = Service(CHROMEDRIVER, service_args=["--verbose", "--log-path=./logfile.log"] )
chrome_options = Options()

chrome_options.add_argument("--headless")
chrome_options.add_argument('--no-sandbox')

driver = webdriver.Chrome(service=s, options=chrome_options) #take note for the new syntax in Bookworm!
driver.implicitly_wait(20) #wait a bit to get missing DOM elements into the page
driver.get(args.url)
driver.set_script_timeout(3600)

# scroll down the page, wait for a while between each scroll

#Inital page size, or page size on last iteration
page_height_from_scrollheight     = driver.execute_script("return document.body.scrollHeight")
#On each scroll event we are going to get the updated height
updated_page_height_from_scrollheight = 0

#iterate over what we presume an entire page
while page_height_from_scrollheight != updated_page_height_from_scrollheight:
    # Scroll to the end of the vertical end
    driver.execute_script('window.scrollTo(0,document.body.scrollHeight,"smooth");')
    time.sleep(1.8)
    updated_page_height_from_scrollheight = driver.execute_script("return document.body.scrollHeight")
    page_height_from_scrollheight = updated_page_height_from_scrollheight
    # As we do not wish to hog the server, limit our new requests for a safe time period


#As we are doing a screenshot, we need to have height and width of the screen , set size to last known size.
driver.set_window_size(1280,updated_page_height_from_scrollheight)
driver.save_screenshot(outputfilename)
time.sleep(10) # give enough time for selenium to flush the file to disk
driver.quit()

נ.ב. לגאון הבא שדוחף ממשק ווב למכשיר שלא אמור להחזיק ממשק וובי למכשיר שיש לו מסך, שים את הממשק במסך או יותר טוב, פשוט תשתמש בפיקוד אנלוגי אם אתה לא מוסגל. !

יום ראשון, דצמבר 15, 2013

משלוח קבצים ב MMS

נאלצתי לבצע העברת קבצים די גדולה לפני זמן מה וחבילת האינטרנט בנייד הסתיימה.
מכיוון שאני די תקוע עם linmodem ואין לי יכולת לפתוח קישור ע"ג gnuradio גמלה בלבי ההחלטה להשתמש בMMS בשביל לשלוח את הקבצים.

מה שעשיתי היה להתקין את python-messaging ולכתוב תסריט פשוט שיבצע שליחה של הקבצים.
בחרתי לשלוח ע"ג תמונה בגלל היכולת שלgif לאחסן קבצים אחרים ועדיין לעבור תחת הידיים של האנטיספאמרים הידועים לי.

תוצאות:

בדקתי עם מספר ספקים ושליחה של מ"ב ספורים (עד 3 לקובץ מקודד) עברה בצורה תקינה.

לא הייתי ממליץ לשלוח ג"ב בצורה כזאת (כי בכל זאת MMSים עולים כסף - אם אני מבין נכון זה נע באזור ה60 אגורות ליחידה) ואצל חלק מהמפעילים גודל ההודעות מתומכר גם כן.

תזהרו לא להספים או לשלוח לרשומות תפוצה (זה לא שימוש הוגן והספק כנראה ינתק אותכם).

דברים שצריך לדעת לפני :

הapn
הsmsc

התסריט עצמו:

#!/usr/bin/python
 

from messaging.mms.message import MMSMessage, MMSMessagePage

from argparse import RawTextHelpFormatter
import argparse

import socket    
import cStringIO # for the response

import sys,os #needed for file size + exit
import errno  #error handling
import getpass#to log the user name

parser = argparse.ArgumentParser(description='Send mms image to an email or a PSTN',formatter_class=RawTextHelpFormatter,
     epilog="""
 Scrapped from the docs by Boris Shtrasman (license GPLv2 as the python-messaging)

 1. In case you don't have a public access to mmsc (like I do): 

 get a cable and connect your phone to the pc,find out what are your mmsc and apn
 
 my mms apn is mms.provider.fr (look for mms apn configuration at your provider.tld)
 wvdial.conf example: 
    [Dialer mms]
    Phone = *99***1#
    Username = fillmeifneed #if your provider uses a username for mms fill it up
    Password = fillmeifneed #if your provider uses a password for mms fill it up
    Stupid Mode = 1
    Dial Command = ATDT
    Modem = /dev/ttyACM0
    Baud = 460800
    Init2 = ATZ
    Init3 = ATE0V1&D2&C1S0=0+IFC=2,2
    ISDN = 0
    Modem Type = Analog Modem
    Init5 =AT+CGDCONT=1,"IP","mms.provider.fr";
 
 run wvdial 
 ~/sudo screen wvdial mms
 
 You would need to check your routing as both the mms provider and your internet provider would\
 give you an ip address.
 
 2. 
 
 find out what is the mmsc address ,don't relay on name as some provider will not resolve it in my example it is 192.168.10.200
 every mms provider has it own port mine used 8080 (other use 80, 9090 and many others)

 In order to pass normal files zip them and add into a gif : 
    cat ~/somegif.gif >> /tmp/tobesent.gif
    zip -0 archive.zip stufftosend/* 
    cat ./archive >> /tmp/tobesent.gif
    
 to send to an email to user at example dot  net when your provider uses email  : 
    send -s 192.168.10.200 --port 8080 -i /tmp/tobesent.gif -d "user@example.net"  -a /mmsc -b "rename the gif to zip"
 to send an email to a phone number 
    send -s 192.168.10.200 --port 8080 -i /tmp/tobesent.gif -d "+3362123123"  -a /mmsc -b "rename the gif to zip" """)
     
     
parser.add_argument('-s','--mms_host', help='mmsc host',required=True)
parser.add_argument('-p','--port',help='mmsc port',type=int, required=True)
parser.add_argument('-i','--input_file',help='file to send as image', required=True)
parser.add_argument('-d','--destination',help='destination in PLMN or email', required=True)
parser.add_argument('-a','--mms_address',help='the mms address to use (commenly just /) some will have http://ip:port/path', required=True)
parser.add_argument('-c','--cc',help=' CC optional ', required=False)
parser.add_argument('-b','--subject',help=' subject  ', required=False)

args = parser.parse_args()

cc_destination = ''
if  args.cc: cc_destination = args.cc
subject = ''
if args.subject: subject=args.subject

print "As you wish %s, I will send the file:%s[%u kb] to %s and cc %s subject (%s)" % (getpass.getuser(),args.input_file,os.stat(args.input_file).st_size / 1024,args.destination,cc_destination,subject)
  
mms_page = MMSMessagePage()
mms_page.add_image(args.input_file)

mms = MMSMessage()
mms.headers['To'] = args.destination
mms.headers['Cc'] = cc_destination
mms.headers['Message-Type'] = 'm-send-req'
mms.headers['Subject'] = subject
mms.add_page(mms_page)
print "will encode '%s' (preparing what would be sent to the destination)" % args.input_file
encoded_mms = mms.encode()
print "message had been encoded"
mmsc_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "connecting to %s:%u" % (args.mms_host , args.port)
try:
  mmsc_socket.connect((args.mms_host, args.port))
  
except socket.timeout:
  print "A timeout to %s:%d was set, make sure you can access it (routes, packet loss etc)" % (args.mms_host, args.port)
  sys.exit()
except KeyboardInterrupt:
  print "Don't be so hard on %s:%d it should work ... " % (args.mms_host, args.port)
  sys.exit()
except socket.error as serror:
  if serror.errno == errno.ECONNREFUSED:
    print "your provider prevent accessing to this mmsc %s have you checked you are using the correct credntials ?" %  (args.mms_host, args.port)
    sys.exit()
  if serror.errno == errno.ETIMEDOUT:
    print "I'm sorry but you need to check your routes to %s, then check your fw doesn't block unreachable destination, when all fail contact customer service" % (args.mms_host)
    sys.exit()

  print "A timeout to %s:%d was set, make sure you can access it (routes, packet loss etc) (%s)" % (args.mms_host, args.port, serror.errno)
  sys.exit()

  
print "was able to connect to %s:%u" % (args.mms_host , args.port)
mmsc_socket.send("POST %s HTTP/1.0\r\n" % args.mms_address)
print "POST command sent to the server"
mmsc_socket.send("Content-Type: application/vnd.wap.mms-message\r\n")
mmsc_socket.send("Content-Length: %d\r\n\r\n" % len(encoded_mms))
print "an encoded mms is on the way to the server , please wait : as %d bytes are on their way to the server and that will take some time" % len (encoded_mms)
try:
 mmsc_socket.sendall(encoded_mms)
except socket.error as serror:
  print "Try to use a smaller file, or check your connection as I couldn't finish the transfer %s" % serror.errno
  sys.exit()
except KeyboardInterrupt:
  mmsc_socket.close()
  print "Don't be so hard on %s:%d it should work ... " % (args.mms_host, args.port)
  sys.exit()
  
print "encoded mms had been sent "
serverResponseStream = cStringIO.StringIO()

while True:
    data = mmsc_socket.recv(4096)
    if not data:
        break

    serverResponseStream.write(data)

mmsc_socket.close()
data = serverResponseStream.getvalue()
serverResponseStream.close()

print "PROXY RESPONSE", data

יום שישי, יוני 05, 2009

פרוייקט בTesting

בפרוייקט שלנו בTesting התבקשנו לפתח מערכת שרת לקוח בנקאית שמדברת עם מערכת של בסיסי נתונים.
מכיוון שאני איש קוד פתוח כמעט כל הטכנולוגיות יהיו תחת תחת רישיונות חופשים (פרט לקטעי קוד ב C# שאינני יודע מה הרישוי המתאים).

הכלים הנדרשים בשביל הפרוייקט -
לריצה :

מערכת דביאן התקנה בסיסית.
שרת mysql5.5
מפרש פייתון 2.5

לפיתוח:
אנו (אני ולאוניד) משתמשים במערכות kdevelop ו Visual Studio 2008 אולם אין שום השפעה לסביבת הפיתוח על הקוד.
הקוד משתמש בשני סוגים של Makefile אחד של cmake ואחד של VS2008.

בחרתי לפתח בפייתון בגלל שזה זמן מצויין ללמוד עוד שפה ועוד טכנולגיות.

צורת העבודה היא שימוש ב XMLRPC בשביל להעביר את המידע בן תוכנת הלקוח לתוכנת השרת.
כאשר שרת שמקבל פנויות XMLRPC הוא עובד מול בסיס הנתונים.

עדיין לא למדתי איך להגן על ההודעות ה XML (אני חושב להעביר אותן בתוך tunnel של SSH) מפני בעיות.
בחרתי בסגנון הזה לאחר שהתייעצתי עם מפתח נוסף עידו קנר שהציע מספר פתרונות להעברת האובייקטים והמליץ על הסגנון הזה.

ולת'כלס:
על מנת להרים שרת xmlrpc פשוט אפשר להשתמש בקוד פייתון הבא :


#! /usr/bin/env python

import SocketServer
from SimpleXMLRPCServer import SimpleXMLRPCServer,SimpleXMLRPCRequestHandler

# Threaded mix-in
# Use it as asynchronic (Fire and forget)
class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass

# The object that will be used to publish the functions
class TestObject:
# Just some foo function
def foo(self, x, y):
s = str(x)
return "Fooish function with (" + str(x) + "," + str(y) + ")"


# Instantiate and bind to localhost:8080
server = AsyncXMLRPCServer(('', 8080), SimpleXMLRPCRequestHandler)

# Registar the listMethod function
server.register_introspection_functions()

# Register example object instance
server.register_instance(TestObject())

# run!
server.serve_forever()


לאחר מכאן נרצה להוסיף קצת התחברות של MySQL:


#! /usr/bin/env python

import SocketServer
import MySQLdb

from SimpleXMLRPCServer import SimpleXMLRPCServer,SimpleXMLRPCRequestHandler

# Threaded mix-in
# Use it as asynchronic (Fire and forget)
class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass

# The object that will be used to publish the functions
class TestObject:
# Just some foo function
def foo(self, x, y):
s = str(x)
return "Fooish function with (" + str(x) + "," + str(y) + ")"

# Make a connection for each query
def showTables(self):
Con = MySQLdb.Connect(host="127.0.0.1", port=3306, user="user", passwd="password")
Cursor = Con.cursor()
sql = "show databases;"
Cursor.execute(sql)
Results = Cursor.fetchall()
Con.close()
return Results


# Instantiate and bind to localhost:8080
server = AsyncXMLRPCServer(('', 8080), SimpleXMLRPCRequestHandler)

# Registar the listMethod function
server.register_introspection_functions()

# Register example object instance
server.register_instance(TestObject())

# run!
server.serve_forever()

בחרתי להשתמש בpass רק בשביל שיטת השגר ושקח (ואולי זה לא ממש נכון וצריך להשתמש באותו הconnection) הרעיון הכללי הוא לתת ל MySql לשבור את הראש בניהול החיבורים.
צריך לעבוד על זה עוד קצת בשביל להביא את זה למצב תקין וטוב.


לעצלני ה C# ניתן הקטע הבא (לקוח ) - אני בטוח שתסדרו מצויין (xmlrpc.net) :

using CookComputing.XmlRpc;

public struct SumAndDiffValue
{
public int sum;
public int difference;
}

[XmlRpcUrl("http://www.cookcomputing.com/sumAndDiff.rem")]
public interface ISumAndDiff : IXmlRpcProxy
{
[XmlRpcMethod]
SumAndDiffValue SumAndDifference(int x, int y);
}

איך מרימים מערכת ?
משתמשים בסביבת הוירטואליזציה החביבה עליכם אני אישית מעדיף את qemu אבל כל אחד יכול להשתמש במה שהוא הוא אוהב.
אז :


qemu-img create -f qcow foo.img 1GB
qemu -boot d -cdrom debian-501-i386-netinst.iso -hda foo.img

לאחר התקנה של כל התלויות שירשמו והתקנת החבילות של הסביבה עצמה .

ה Testing עצמו :
יתבצע באמצעות TAP או cucumber או כל סביבה אחרת שהבודקים ירצו (רק שתיהיה חופשית).